-
272.github/workflows/auto-tag-release.yml
-
20.gitignore
-
92.goreleaser.yml
-
21LICENSE
-
604README.md
-
584README_CN.md
-
608README_JP.md
-
328cmd/cursor-id-modifier/main.go
-
15go.mod
-
26go.sum
-
BINimg/qun-10.jpg
-
BINimg/qun-12.png
-
BINimg/qun-14.jpg
-
BINimg/qun-8.png
-
BINimg/qun11.jpg
-
BINimg/qun13.png
-
BINimg/qun9.png
-
BINimg/wx_group.jpg
-
BINimg/wx_group6.jpg
-
BINimg/wx_group7.jpg
-
BINimg/wx_public.jpg
-
BINimg/wx_zsm.jpg
-
BINimg/zanzhu/twillot.png
-
153internal/config/config.go
-
187internal/lang/lang.go
-
216internal/process/manager.go
-
94internal/ui/display.go
-
20internal/ui/logo.go
-
122internal/ui/spinner.go
-
116pkg/idgen/generator.go
-
375process_cursor_links.py
-
74scripts/build_all.bat
-
143scripts/build_all.sh
-
318scripts/cursor_id_modifier.pot
-
1298scripts/cursor_id_modifier.py
-
9scripts/git-actions.sh
-
193scripts/install.ps1
-
127scripts/install.sh
-
1293scripts/run/cursor_linux_id_modifier.sh
-
1401scripts/run/cursor_mac_free_trial_reset.sh
-
3210scripts/run/cursor_mac_id_modifier.sh
-
688scripts/run/cursor_mac_id_modifier_new.sh
-
1886scripts/run/cursor_win_id_modifier.ps1
-
607scripts/run/cursor_win_id_modifier_old.ps1
@ -1,272 +0,0 @@ |
|||||
# This workflow requires Ubuntu 22.04 or 24.04 |
|
||||
|
|
||||
name: Auto Tag & Release |
|
||||
|
|
||||
on: |
|
||||
push: |
|
||||
branches: |
|
||||
- master |
|
||||
- main |
|
||||
tags: |
|
||||
- "v*" |
|
||||
paths-ignore: |
|
||||
- "**.md" |
|
||||
- "LICENSE" |
|
||||
- ".gitignore" |
|
||||
workflow_call: {} |
|
||||
|
|
||||
permissions: |
|
||||
contents: write |
|
||||
packages: write |
|
||||
actions: write |
|
||||
|
|
||||
jobs: |
|
||||
pre_job: |
|
||||
runs-on: ubuntu-22.04 |
|
||||
outputs: |
|
||||
should_skip: ${{ steps.skip_check.outputs.should_skip }} |
|
||||
steps: |
|
||||
- id: skip_check |
|
||||
uses: fkirc/skip-duplicate-actions@v5.3.0 |
|
||||
with: |
|
||||
cancel_others: "true" |
|
||||
concurrent_skipping: "same_content" |
|
||||
|
|
||||
auto-tag-release: |
|
||||
needs: pre_job |
|
||||
if: | |
|
||||
needs.pre_job.outputs.should_skip != 'true' || |
|
||||
startsWith(github.ref, 'refs/tags/v') |
|
||||
runs-on: ubuntu-22.04 |
|
||||
timeout-minutes: 15 |
|
||||
outputs: |
|
||||
version: ${{ steps.get_latest_tag.outputs.version }} |
|
||||
concurrency: |
|
||||
group: ${{ github.workflow }}-${{ github.ref }} |
|
||||
cancel-in-progress: true |
|
||||
|
|
||||
steps: |
|
||||
- name: Checkout |
|
||||
uses: actions/checkout@v3 |
|
||||
with: |
|
||||
fetch-depth: 0 |
|
||||
lfs: true |
|
||||
submodules: recursive |
|
||||
|
|
||||
- name: Setup Go |
|
||||
uses: actions/setup-go@v3 |
|
||||
with: |
|
||||
go-version: "1.21" |
|
||||
check-latest: true |
|
||||
cache: true |
|
||||
|
|
||||
- name: Cache |
|
||||
uses: actions/cache@v3 |
|
||||
with: |
|
||||
path: | |
|
||||
~/.cache/go-build |
|
||||
~/go/pkg/mod |
|
||||
~/.cache/git |
|
||||
key: ${{ runner.os }}-build-${{ hashFiles('**/go.sum') }} |
|
||||
restore-keys: | |
|
||||
${{ runner.os }}-build- |
|
||||
${{ runner.os }}- |
|
||||
|
|
||||
# 只在非tag推送时执行自动打tag |
|
||||
- name: Get latest tag |
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }} |
|
||||
id: get_latest_tag |
|
||||
run: | |
|
||||
set -euo pipefail |
|
||||
git fetch --tags --force || { |
|
||||
echo "::error::Failed to fetch tags" |
|
||||
exit 1 |
|
||||
} |
|
||||
latest_tag=$(git tag -l 'v*' --sort=-v:refname | head -n 1) |
|
||||
if [ -z "$latest_tag" ]; then |
|
||||
new_version="v0.1.0" |
|
||||
else |
|
||||
major=$(echo $latest_tag | cut -d. -f1) |
|
||||
minor=$(echo $latest_tag | cut -d. -f2) |
|
||||
patch=$(echo $latest_tag | cut -d. -f3) |
|
||||
new_patch=$((patch + 1)) |
|
||||
new_version="$major.$minor.$new_patch" |
|
||||
fi |
|
||||
echo "version=$new_version" >> "$GITHUB_OUTPUT" |
|
||||
echo "Generated version: $new_version" |
|
||||
|
|
||||
- name: Validate version |
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }} |
|
||||
run: | |
|
||||
set -euo pipefail |
|
||||
new_tag="${{ steps.get_latest_tag.outputs.version }}" |
|
||||
echo "Validating version: $new_tag" |
|
||||
if [[ ! $new_tag =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then |
|
||||
echo "::error::Invalid version format: $new_tag" |
|
||||
exit 1 |
|
||||
fi |
|
||||
major=$(echo $new_tag | cut -d. -f1 | tr -d 'v') |
|
||||
minor=$(echo $new_tag | cut -d. -f2) |
|
||||
patch=$(echo $new_tag | cut -d. -f3) |
|
||||
if [[ $major -gt 99 || $minor -gt 99 || $patch -gt 999 ]]; then |
|
||||
echo "::error::Version numbers out of valid range" |
|
||||
exit 1 |
|
||||
fi |
|
||||
echo "Version validation passed" |
|
||||
|
|
||||
- name: Create new tag |
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/v') }} |
|
||||
env: |
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
|
||||
run: | |
|
||||
new_tag=${{ steps.get_latest_tag.outputs.version }} |
|
||||
git config --global user.name 'github-actions[bot]' |
|
||||
git config --global user.email 'github-actions[bot]@users.noreply.github.com' |
|
||||
git tag -a $new_tag -m "Release $new_tag" |
|
||||
git push origin $new_tag |
|
||||
|
|
||||
# 在 Run GoReleaser 之前添加配置检查步骤 |
|
||||
- name: Check GoReleaser config |
|
||||
run: | |
|
||||
if [ ! -f ".goreleaser.yml" ] && [ ! -f ".goreleaser.yaml" ]; then |
|
||||
echo "::error::GoReleaser configuration file not found" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# 添加依赖检查步骤 |
|
||||
- name: Check Dependencies |
|
||||
run: | |
|
||||
go mod verify |
|
||||
go mod download |
|
||||
# 如果使用 vendor 模式,则执行以下命令 |
|
||||
if [ -d "vendor" ]; then |
|
||||
go mod vendor |
|
||||
fi |
|
||||
|
|
||||
# 添加构建环境准备步骤 |
|
||||
- name: Prepare Build Environment |
|
||||
run: | |
|
||||
echo "Building version: ${VERSION:-development}" |
|
||||
echo "GOOS=${GOOS:-$(go env GOOS)}" >> $GITHUB_ENV |
|
||||
echo "GOARCH=${GOARCH:-$(go env GOARCH)}" >> $GITHUB_ENV |
|
||||
echo "GO111MODULE=on" >> $GITHUB_ENV |
|
||||
|
|
||||
# 添加清理步骤 |
|
||||
- name: Cleanup workspace |
|
||||
run: | |
|
||||
rm -rf /tmp/go/ |
|
||||
rm -rf .cache/ |
|
||||
rm -rf dist/ |
|
||||
git clean -fdx |
|
||||
git status |
|
||||
|
|
||||
# 修改 GoReleaser 步骤 |
|
||||
- name: Run GoReleaser |
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }} |
|
||||
uses: goreleaser/goreleaser-action@v3 |
|
||||
with: |
|
||||
distribution: goreleaser |
|
||||
version: latest |
|
||||
args: release --clean --timeout 60m |
|
||||
env: |
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
|
||||
VERSION: ${{ steps.get_latest_tag.outputs.version }} |
|
||||
CGO_ENABLED: 0 |
|
||||
GOPATH: /tmp/go |
|
||||
GOROOT: ${{ env.GOROOT }} |
|
||||
GOCACHE: /tmp/.cache/go-build |
|
||||
GOMODCACHE: /tmp/go/pkg/mod |
|
||||
GORELEASER_DEBUG: 1 |
|
||||
GORELEASER_CURRENT_TAG: ${{ steps.get_latest_tag.outputs.version }} |
|
||||
# 添加额外的构建信息 |
|
||||
BUILD_TIME: ${{ steps.get_latest_tag.outputs.version }} |
|
||||
BUILD_COMMIT: ${{ github.sha }} |
|
||||
|
|
||||
# 优化 vendor 同步步骤 |
|
||||
- name: Sync vendor directory |
|
||||
run: | |
|
||||
echo "Syncing vendor directory..." |
|
||||
go mod tidy |
|
||||
go mod vendor |
|
||||
go mod verify |
|
||||
# 验证 vendor 目录 |
|
||||
if [ -d "vendor" ]; then |
|
||||
echo "Verifying vendor directory..." |
|
||||
go mod verify |
|
||||
# 检查是否有未跟踪的文件 |
|
||||
if [ -n "$(git status --porcelain vendor/)" ]; then |
|
||||
echo "Warning: Vendor directory has uncommitted changes" |
|
||||
git status vendor/ |
|
||||
fi |
|
||||
fi |
|
||||
|
|
||||
# 添加错误检查步骤 |
|
||||
- name: Check GoReleaser Output |
|
||||
if: failure() |
|
||||
run: | |
|
||||
echo "::group::GoReleaser Debug Info" |
|
||||
cat dist/artifacts.json || true |
|
||||
echo "::endgroup::" |
|
||||
|
|
||||
echo "::group::GoReleaser Config" |
|
||||
cat .goreleaser.yml |
|
||||
echo "::endgroup::" |
|
||||
|
|
||||
echo "::group::Environment Info" |
|
||||
go version |
|
||||
go env |
|
||||
echo "::endgroup::" |
|
||||
|
|
||||
- name: Set Release Version |
|
||||
if: startsWith(github.ref, 'refs/tags/v') |
|
||||
run: | |
|
||||
echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV |
|
||||
|
|
||||
# 改进验证步骤 |
|
||||
- name: Verify Release |
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/v') || (success() && steps.get_latest_tag.outputs.version != '') }} |
|
||||
run: | |
|
||||
echo "Verifying release artifacts..." |
|
||||
if [ ! -d "dist" ]; then |
|
||||
echo "::error::Release artifacts not found" |
|
||||
exit 1 |
|
||||
fi |
|
||||
# 验证生成的二进制文件 |
|
||||
for file in dist/cursor-id-modifier_*; do |
|
||||
if [ -f "$file" ]; then |
|
||||
echo "Verifying: $file" |
|
||||
if [[ "$file" == *.exe ]]; then |
|
||||
# Windows 二进制文件检查 |
|
||||
if ! [ -x "$file" ]; then |
|
||||
echo "::error::$file is not executable" |
|
||||
exit 1 |
|
||||
fi |
|
||||
else |
|
||||
# Unix 二进制文件检查 |
|
||||
if ! [ -x "$file" ]; then |
|
||||
echo "::error::$file is not executable" |
|
||||
exit 1 |
|
||||
fi |
|
||||
fi |
|
||||
fi |
|
||||
done |
|
||||
|
|
||||
- name: Notify on failure |
|
||||
if: failure() |
|
||||
run: | |
|
||||
echo "::error::Release process failed" |
|
||||
|
|
||||
# 修改构建摘要步骤 |
|
||||
- name: Build Summary |
|
||||
if: always() |
|
||||
run: | |
|
||||
echo "## Build Summary" >> $GITHUB_STEP_SUMMARY |
|
||||
echo "- Go Version: $(go version)" >> $GITHUB_STEP_SUMMARY |
|
||||
echo "- Release Version: ${VERSION:-N/A}" >> $GITHUB_STEP_SUMMARY |
|
||||
echo "- GPG Signing: Disabled" >> $GITHUB_STEP_SUMMARY |
|
||||
echo "- Build Status: ${{ job.status }}" >> $GITHUB_STEP_SUMMARY |
|
||||
|
|
||||
if [ -d "dist" ]; then |
|
||||
echo "### Generated Artifacts" >> $GITHUB_STEP_SUMMARY |
|
||||
ls -lh dist/ | awk '{print "- "$9" ("$5")"}' >> $GITHUB_STEP_SUMMARY |
|
||||
fi |
|
@ -1,92 +0,0 @@ |
|||||
version: 2 |
|
||||
|
|
||||
before: |
|
||||
hooks: |
|
||||
- go mod tidy |
|
||||
- go mod vendor |
|
||||
- go mod verify |
|
||||
|
|
||||
builds: |
|
||||
- id: cursor-id-modifier |
|
||||
main: ./cmd/cursor-id-modifier/main.go |
|
||||
binary: cursor-id-modifier |
|
||||
env: |
|
||||
- CGO_ENABLED=0 |
|
||||
- GO111MODULE=on |
|
||||
goos: |
|
||||
- linux |
|
||||
- windows |
|
||||
- darwin |
|
||||
goarch: |
|
||||
- amd64 |
|
||||
- arm64 |
|
||||
- "386" |
|
||||
ignore: |
|
||||
- goos: darwin |
|
||||
goarch: "386" |
|
||||
ldflags: |
|
||||
- -s -w |
|
||||
- -X 'main.version={{.Version}}' |
|
||||
- -X 'main.commit={{.ShortCommit}}' |
|
||||
- -X 'main.date={{.CommitDate}}' |
|
||||
- -X 'main.builtBy=goreleaser' |
|
||||
flags: |
|
||||
- -trimpath |
|
||||
mod_timestamp: '{{ .CommitTimestamp }}' |
|
||||
|
|
||||
archives: |
|
||||
- id: binary |
|
||||
format: binary |
|
||||
name_template: >- |
|
||||
{{- .ProjectName }}_ |
|
||||
{{- .Version }}_ |
|
||||
{{- .Os }}_ |
|
||||
{{- if eq .Arch "amd64" }}x86_64 |
|
||||
{{- else if eq .Arch "386" }}i386 |
|
||||
{{- else }}{{ .Arch }}{{ end }} |
|
||||
builds: |
|
||||
- cursor-id-modifier |
|
||||
allow_different_binary_count: true |
|
||||
files: |
|
||||
- none* |
|
||||
|
|
||||
checksum: |
|
||||
name_template: 'checksums.txt' |
|
||||
algorithm: sha256 |
|
||||
|
|
||||
release: |
|
||||
draft: true |
|
||||
prerelease: auto |
|
||||
mode: replace |
|
||||
header: | |
|
||||
## Release {{.Tag}} ({{.Date}}) |
|
||||
|
|
||||
See [CHANGELOG.md](CHANGELOG.md) for details. |
|
||||
footer: | |
|
||||
**Full Changelog**: https://github.com/owner/repo/compare/{{ .PreviousTag }}...{{ .Tag }} |
|
||||
extra_files: |
|
||||
- glob: 'LICENSE*' |
|
||||
- glob: 'README*' |
|
||||
- glob: 'CHANGELOG*' |
|
||||
|
|
||||
changelog: |
|
||||
sort: asc |
|
||||
use: github |
|
||||
filters: |
|
||||
exclude: |
|
||||
- '^docs:' |
|
||||
- '^test:' |
|
||||
- '^ci:' |
|
||||
- Merge pull request |
|
||||
- Merge branch |
|
||||
groups: |
|
||||
- title: Features |
|
||||
regexp: "^.*feat[(\\w)]*:+.*$" |
|
||||
order: 0 |
|
||||
- title: 'Bug fixes' |
|
||||
regexp: "^.*fix[(\\w)]*:+.*$" |
|
||||
order: 1 |
|
||||
- title: Others |
|
||||
order: 999 |
|
||||
|
|
||||
project_name: cursor-id-modifier |
|
@ -1,21 +0,0 @@ |
|||||
MIT License |
|
||||
|
|
||||
Copyright (c) 2024 dacrab |
|
||||
|
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
|
||||
of this software and associated documentation files (the "Software"), to deal |
|
||||
in the Software without restriction, including without limitation the rights |
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
|
||||
copies of the Software, and to permit persons to whom the Software is |
|
||||
furnished to do so, subject to the following conditions: |
|
||||
|
|
||||
The above copyright notice and this permission notice shall be included in all |
|
||||
copies or substantial portions of the Software. |
|
||||
|
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
|
||||
SOFTWARE. |
|
@ -1,328 +0,0 @@ |
|||||
package main |
|
||||
|
|
||||
import ( |
|
||||
"bufio" |
|
||||
"flag" |
|
||||
"fmt" |
|
||||
"os" |
|
||||
"os/exec" |
|
||||
"os/user" |
|
||||
"runtime" |
|
||||
"runtime/debug" |
|
||||
"strings" |
|
||||
|
|
||||
"github.com/sirupsen/logrus" |
|
||||
|
|
||||
"github.com/yuaotian/go-cursor-help/internal/config" |
|
||||
"github.com/yuaotian/go-cursor-help/internal/lang" |
|
||||
"github.com/yuaotian/go-cursor-help/internal/process" |
|
||||
"github.com/yuaotian/go-cursor-help/internal/ui" |
|
||||
"github.com/yuaotian/go-cursor-help/pkg/idgen" |
|
||||
) |
|
||||
|
|
||||
// Global variables
|
|
||||
var ( |
|
||||
version = "dev" |
|
||||
setReadOnly = flag.Bool("r", false, "set storage.json to read-only mode") |
|
||||
showVersion = flag.Bool("v", false, "show version information") |
|
||||
log = logrus.New() |
|
||||
) |
|
||||
|
|
||||
func main() { |
|
||||
// Place defer at the beginning of main to ensure it can catch panics from all subsequent function calls
|
|
||||
defer func() { |
|
||||
if r := recover(); r != nil { |
|
||||
log.Errorf("Panic recovered: %v\n", r) |
|
||||
debug.PrintStack() |
|
||||
waitExit() |
|
||||
} |
|
||||
}() |
|
||||
|
|
||||
handleFlags() |
|
||||
setupLogger() |
|
||||
|
|
||||
username := getCurrentUser() |
|
||||
log.Debug("Running as user:", username) |
|
||||
|
|
||||
// Initialize components
|
|
||||
display := ui.NewDisplay(nil) |
|
||||
configManager := initConfigManager(username) |
|
||||
generator := idgen.NewGenerator() |
|
||||
processManager := process.NewManager(nil, log) |
|
||||
|
|
||||
// Check and handle privileges
|
|
||||
if err := handlePrivileges(display); err != nil { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Setup display
|
|
||||
setupDisplay(display) |
|
||||
|
|
||||
text := lang.GetText() |
|
||||
|
|
||||
// Handle Cursor processes
|
|
||||
if err := handleCursorProcesses(display, processManager); err != nil { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Handle configuration
|
|
||||
oldConfig := readExistingConfig(display, configManager, text) |
|
||||
newConfig := generateNewConfig(display, generator, oldConfig, text) |
|
||||
|
|
||||
if err := saveConfiguration(display, configManager, newConfig); err != nil { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// Show completion messages
|
|
||||
showCompletionMessages(display) |
|
||||
|
|
||||
if os.Getenv("AUTOMATED_MODE") != "1" { |
|
||||
waitExit() |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func handleFlags() { |
|
||||
flag.Parse() |
|
||||
if *showVersion { |
|
||||
fmt.Printf("Cursor ID Modifier v%s\n", version) |
|
||||
os.Exit(0) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func setupLogger() { |
|
||||
log.SetFormatter(&logrus.TextFormatter{ |
|
||||
FullTimestamp: true, |
|
||||
DisableLevelTruncation: true, |
|
||||
PadLevelText: true, |
|
||||
}) |
|
||||
log.SetLevel(logrus.InfoLevel) |
|
||||
} |
|
||||
|
|
||||
func getCurrentUser() string { |
|
||||
if username := os.Getenv("SUDO_USER"); username != "" { |
|
||||
return username |
|
||||
} |
|
||||
|
|
||||
user, err := user.Current() |
|
||||
if err != nil { |
|
||||
log.Fatal(err) |
|
||||
} |
|
||||
return user.Username |
|
||||
} |
|
||||
|
|
||||
func initConfigManager(username string) *config.Manager { |
|
||||
configManager, err := config.NewManager(username) |
|
||||
if err != nil { |
|
||||
log.Fatal(err) |
|
||||
} |
|
||||
return configManager |
|
||||
} |
|
||||
|
|
||||
func handlePrivileges(display *ui.Display) error { |
|
||||
isAdmin, err := checkAdminPrivileges() |
|
||||
if err != nil { |
|
||||
log.Error(err) |
|
||||
waitExit() |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
if !isAdmin { |
|
||||
if runtime.GOOS == "windows" { |
|
||||
return handleWindowsPrivileges(display) |
|
||||
} |
|
||||
display.ShowPrivilegeError( |
|
||||
lang.GetText().PrivilegeError, |
|
||||
lang.GetText().RunWithSudo, |
|
||||
lang.GetText().SudoExample, |
|
||||
) |
|
||||
waitExit() |
|
||||
return fmt.Errorf("insufficient privileges") |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func handleWindowsPrivileges(display *ui.Display) error { |
|
||||
message := "\nRequesting administrator privileges..." |
|
||||
if lang.GetCurrentLanguage() == lang.CN { |
|
||||
message = "\n请求管理员权限..." |
|
||||
} |
|
||||
fmt.Println(message) |
|
||||
|
|
||||
if err := selfElevate(); err != nil { |
|
||||
log.Error(err) |
|
||||
display.ShowPrivilegeError( |
|
||||
lang.GetText().PrivilegeError, |
|
||||
lang.GetText().RunAsAdmin, |
|
||||
lang.GetText().RunWithSudo, |
|
||||
lang.GetText().SudoExample, |
|
||||
) |
|
||||
waitExit() |
|
||||
return err |
|
||||
} |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func setupDisplay(display *ui.Display) { |
|
||||
if err := display.ClearScreen(); err != nil { |
|
||||
log.Warn("Failed to clear screen:", err) |
|
||||
} |
|
||||
display.ShowLogo() |
|
||||
fmt.Println() |
|
||||
} |
|
||||
|
|
||||
func handleCursorProcesses(display *ui.Display, processManager *process.Manager) error { |
|
||||
if os.Getenv("AUTOMATED_MODE") == "1" { |
|
||||
log.Debug("Running in automated mode, skipping Cursor process closing") |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
display.ShowProgress("Closing Cursor...") |
|
||||
log.Debug("Attempting to close Cursor processes") |
|
||||
|
|
||||
if err := processManager.KillCursorProcesses(); err != nil { |
|
||||
log.Error("Failed to close Cursor:", err) |
|
||||
display.StopProgress() |
|
||||
display.ShowError("Failed to close Cursor. Please close it manually and try again.") |
|
||||
waitExit() |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
if processManager.IsCursorRunning() { |
|
||||
log.Error("Cursor processes still detected after closing") |
|
||||
display.StopProgress() |
|
||||
display.ShowError("Failed to close Cursor completely. Please close it manually and try again.") |
|
||||
waitExit() |
|
||||
return fmt.Errorf("cursor still running") |
|
||||
} |
|
||||
|
|
||||
log.Debug("Successfully closed all Cursor processes") |
|
||||
display.StopProgress() |
|
||||
fmt.Println() |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func readExistingConfig(display *ui.Display, configManager *config.Manager, text lang.TextResource) *config.StorageConfig { |
|
||||
fmt.Println() |
|
||||
display.ShowProgress(text.ReadingConfig) |
|
||||
oldConfig, err := configManager.ReadConfig() |
|
||||
if err != nil { |
|
||||
log.Warn("Failed to read existing config:", err) |
|
||||
oldConfig = nil |
|
||||
} |
|
||||
display.StopProgress() |
|
||||
fmt.Println() |
|
||||
return oldConfig |
|
||||
} |
|
||||
|
|
||||
func generateNewConfig(display *ui.Display, generator *idgen.Generator, oldConfig *config.StorageConfig, text lang.TextResource) *config.StorageConfig { |
|
||||
display.ShowProgress(text.GeneratingIds) |
|
||||
newConfig := &config.StorageConfig{} |
|
||||
|
|
||||
if machineID, err := generator.GenerateMachineID(); err != nil { |
|
||||
log.Fatal("Failed to generate machine ID:", err) |
|
||||
} else { |
|
||||
newConfig.TelemetryMachineId = machineID |
|
||||
} |
|
||||
|
|
||||
if macMachineID, err := generator.GenerateMacMachineID(); err != nil { |
|
||||
log.Fatal("Failed to generate MAC machine ID:", err) |
|
||||
} else { |
|
||||
newConfig.TelemetryMacMachineId = macMachineID |
|
||||
} |
|
||||
|
|
||||
if deviceID, err := generator.GenerateDeviceID(); err != nil { |
|
||||
log.Fatal("Failed to generate device ID:", err) |
|
||||
} else { |
|
||||
newConfig.TelemetryDevDeviceId = deviceID |
|
||||
} |
|
||||
|
|
||||
if oldConfig != nil && oldConfig.TelemetrySqmId != "" { |
|
||||
newConfig.TelemetrySqmId = oldConfig.TelemetrySqmId |
|
||||
} else if sqmID, err := generator.GenerateSQMID(); err != nil { |
|
||||
log.Fatal("Failed to generate SQM ID:", err) |
|
||||
} else { |
|
||||
newConfig.TelemetrySqmId = sqmID |
|
||||
} |
|
||||
|
|
||||
display.StopProgress() |
|
||||
fmt.Println() |
|
||||
return newConfig |
|
||||
} |
|
||||
|
|
||||
func saveConfiguration(display *ui.Display, configManager *config.Manager, newConfig *config.StorageConfig) error { |
|
||||
display.ShowProgress("Saving configuration...") |
|
||||
if err := configManager.SaveConfig(newConfig, *setReadOnly); err != nil { |
|
||||
log.Error(err) |
|
||||
waitExit() |
|
||||
return err |
|
||||
} |
|
||||
display.StopProgress() |
|
||||
fmt.Println() |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
func showCompletionMessages(display *ui.Display) { |
|
||||
display.ShowSuccess(lang.GetText().SuccessMessage, lang.GetText().RestartMessage) |
|
||||
fmt.Println() |
|
||||
|
|
||||
message := "Operation completed!" |
|
||||
if lang.GetCurrentLanguage() == lang.CN { |
|
||||
message = "操作完成!" |
|
||||
} |
|
||||
display.ShowInfo(message) |
|
||||
} |
|
||||
|
|
||||
func waitExit() { |
|
||||
fmt.Print(lang.GetText().PressEnterToExit) |
|
||||
os.Stdout.Sync() |
|
||||
bufio.NewReader(os.Stdin).ReadString('\n') |
|
||||
} |
|
||||
|
|
||||
func checkAdminPrivileges() (bool, error) { |
|
||||
switch runtime.GOOS { |
|
||||
case "windows": |
|
||||
cmd := exec.Command("net", "session") |
|
||||
return cmd.Run() == nil, nil |
|
||||
|
|
||||
case "darwin", "linux": |
|
||||
currentUser, err := user.Current() |
|
||||
if err != nil { |
|
||||
return false, fmt.Errorf("failed to get current user: %w", err) |
|
||||
} |
|
||||
return currentUser.Uid == "0", nil |
|
||||
|
|
||||
default: |
|
||||
return false, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
func selfElevate() error { |
|
||||
os.Setenv("AUTOMATED_MODE", "1") |
|
||||
|
|
||||
switch runtime.GOOS { |
|
||||
case "windows": |
|
||||
verb := "runas" |
|
||||
exe, _ := os.Executable() |
|
||||
cwd, _ := os.Getwd() |
|
||||
args := strings.Join(os.Args[1:], " ") |
|
||||
|
|
||||
cmd := exec.Command("cmd", "/C", "start", verb, exe, args) |
|
||||
cmd.Dir = cwd |
|
||||
return cmd.Run() |
|
||||
|
|
||||
case "darwin", "linux": |
|
||||
exe, err := os.Executable() |
|
||||
if err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
cmd := exec.Command("sudo", append([]string{exe}, os.Args[1:]...)...) |
|
||||
cmd.Stdin = os.Stdin |
|
||||
cmd.Stdout = os.Stdout |
|
||||
cmd.Stderr = os.Stderr |
|
||||
return cmd.Run() |
|
||||
|
|
||||
default: |
|
||||
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) |
|
||||
} |
|
||||
} |
|
@ -1,15 +0,0 @@ |
|||||
module github.com/yuaotian/go-cursor-help |
|
||||
|
|
||||
go 1.21 |
|
||||
|
|
||||
require ( |
|
||||
github.com/fatih/color v1.15.0 |
|
||||
github.com/sirupsen/logrus v1.9.3 |
|
||||
) |
|
||||
|
|
||||
require ( |
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect |
|
||||
github.com/mattn/go-isatty v0.0.17 // indirect |
|
||||
github.com/stretchr/testify v1.10.0 // indirect |
|
||||
golang.org/x/sys v0.13.0 // indirect |
|
||||
) |
|
@ -1,26 +0,0 @@ |
|||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
|
||||
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= |
|
||||
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= |
|
||||
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= |
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= |
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= |
|
||||
github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= |
|
||||
github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= |
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
|
||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= |
|
||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= |
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= |
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= |
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= |
|
||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= |
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= |
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= |
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
|
Before Width: 937 | Height: 1319 | Size: 128 KiB |
Before Width: 937 | Height: 1319 | Size: 124 KiB |
Before Width: 937 | Height: 1319 | Size: 128 KiB |
Before Width: 937 | Height: 1319 | Size: 147 KiB |
Before Width: 937 | Height: 1319 | Size: 129 KiB |
Before Width: 937 | Height: 1319 | Size: 127 KiB |
Before Width: 937 | Height: 1319 | Size: 126 KiB |
Before Width: 1250 | Height: 1755 | Size: 175 KiB |
Before Width: 1250 | Height: 1755 | Size: 179 KiB |
Before Width: 1250 | Height: 1755 | Size: 181 KiB |
Before Width: 258 | Height: 258 | Size: 27 KiB |
Before Width: 943 | Height: 943 | Size: 60 KiB |
Before Width: 128 | Height: 128 | Size: 11 KiB |
@ -1,153 +0,0 @@ |
|||||
package config |
|
||||
|
|
||||
import ( |
|
||||
"encoding/json" |
|
||||
"fmt" |
|
||||
"os" |
|
||||
"path/filepath" |
|
||||
"runtime" |
|
||||
"sync" |
|
||||
"time" |
|
||||
) |
|
||||
|
|
||||
// StorageConfig represents the storage configuration
|
|
||||
type StorageConfig struct { |
|
||||
TelemetryMacMachineId string `json:"telemetry.macMachineId"` |
|
||||
TelemetryMachineId string `json:"telemetry.machineId"` |
|
||||
TelemetryDevDeviceId string `json:"telemetry.devDeviceId"` |
|
||||
TelemetrySqmId string `json:"telemetry.sqmId"` |
|
||||
LastModified string `json:"lastModified"` |
|
||||
Version string `json:"version"` |
|
||||
} |
|
||||
|
|
||||
// Manager handles configuration operations
|
|
||||
type Manager struct { |
|
||||
configPath string |
|
||||
mu sync.RWMutex |
|
||||
} |
|
||||
|
|
||||
// NewManager creates a new configuration manager
|
|
||||
func NewManager(username string) (*Manager, error) { |
|
||||
configPath, err := getConfigPath(username) |
|
||||
if err != nil { |
|
||||
return nil, fmt.Errorf("failed to get config path: %w", err) |
|
||||
} |
|
||||
return &Manager{configPath: configPath}, nil |
|
||||
} |
|
||||
|
|
||||
// ReadConfig reads the existing configuration
|
|
||||
func (m *Manager) ReadConfig() (*StorageConfig, error) { |
|
||||
m.mu.RLock() |
|
||||
defer m.mu.RUnlock() |
|
||||
|
|
||||
data, err := os.ReadFile(m.configPath) |
|
||||
if err != nil { |
|
||||
if os.IsNotExist(err) { |
|
||||
return nil, nil |
|
||||
} |
|
||||
return nil, fmt.Errorf("failed to read config file: %w", err) |
|
||||
} |
|
||||
|
|
||||
var config StorageConfig |
|
||||
if err := json.Unmarshal(data, &config); err != nil { |
|
||||
return nil, fmt.Errorf("failed to parse config file: %w", err) |
|
||||
} |
|
||||
|
|
||||
return &config, nil |
|
||||
} |
|
||||
|
|
||||
// SaveConfig saves the configuration
|
|
||||
func (m *Manager) SaveConfig(config *StorageConfig, readOnly bool) error { |
|
||||
m.mu.Lock() |
|
||||
defer m.mu.Unlock() |
|
||||
|
|
||||
// Ensure parent directories exist
|
|
||||
if err := os.MkdirAll(filepath.Dir(m.configPath), 0755); err != nil { |
|
||||
return fmt.Errorf("failed to create config directory: %w", err) |
|
||||
} |
|
||||
|
|
||||
// Prepare updated configuration
|
|
||||
updatedConfig := m.prepareUpdatedConfig(config) |
|
||||
|
|
||||
// Write configuration
|
|
||||
if err := m.writeConfigFile(updatedConfig, readOnly); err != nil { |
|
||||
return err |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// prepareUpdatedConfig merges existing config with updates
|
|
||||
func (m *Manager) prepareUpdatedConfig(config *StorageConfig) map[string]interface{} { |
|
||||
// Read existing config
|
|
||||
originalFile := make(map[string]interface{}) |
|
||||
if data, err := os.ReadFile(m.configPath); err == nil { |
|
||||
json.Unmarshal(data, &originalFile) |
|
||||
} |
|
||||
|
|
||||
// Update fields
|
|
||||
originalFile["telemetry.sqmId"] = config.TelemetrySqmId |
|
||||
originalFile["telemetry.macMachineId"] = config.TelemetryMacMachineId |
|
||||
originalFile["telemetry.machineId"] = config.TelemetryMachineId |
|
||||
originalFile["telemetry.devDeviceId"] = config.TelemetryDevDeviceId |
|
||||
originalFile["lastModified"] = time.Now().UTC().Format(time.RFC3339) |
|
||||
// originalFile["version"] = "1.0.1"
|
|
||||
|
|
||||
return originalFile |
|
||||
} |
|
||||
|
|
||||
// writeConfigFile handles the atomic write of the config file
|
|
||||
func (m *Manager) writeConfigFile(config map[string]interface{}, readOnly bool) error { |
|
||||
// Marshal with indentation
|
|
||||
content, err := json.MarshalIndent(config, "", " ") |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("failed to marshal config: %w", err) |
|
||||
} |
|
||||
|
|
||||
// Write to temporary file
|
|
||||
tmpPath := m.configPath + ".tmp" |
|
||||
if err := os.WriteFile(tmpPath, content, 0666); err != nil { |
|
||||
return fmt.Errorf("failed to write temporary file: %w", err) |
|
||||
} |
|
||||
|
|
||||
// Set final permissions
|
|
||||
fileMode := os.FileMode(0666) |
|
||||
if readOnly { |
|
||||
fileMode = 0444 |
|
||||
} |
|
||||
|
|
||||
if err := os.Chmod(tmpPath, fileMode); err != nil { |
|
||||
os.Remove(tmpPath) |
|
||||
return fmt.Errorf("failed to set temporary file permissions: %w", err) |
|
||||
} |
|
||||
|
|
||||
// Atomic rename
|
|
||||
if err := os.Rename(tmpPath, m.configPath); err != nil { |
|
||||
os.Remove(tmpPath) |
|
||||
return fmt.Errorf("failed to rename file: %w", err) |
|
||||
} |
|
||||
|
|
||||
// Sync directory
|
|
||||
if dir, err := os.Open(filepath.Dir(m.configPath)); err == nil { |
|
||||
defer dir.Close() |
|
||||
dir.Sync() |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// getConfigPath returns the path to the configuration file
|
|
||||
func getConfigPath(username string) (string, error) { |
|
||||
var configDir string |
|
||||
switch runtime.GOOS { |
|
||||
case "windows": |
|
||||
configDir = filepath.Join(os.Getenv("APPDATA"), "Cursor", "User", "globalStorage") |
|
||||
case "darwin": |
|
||||
configDir = filepath.Join("/Users", username, "Library", "Application Support", "Cursor", "User", "globalStorage") |
|
||||
case "linux": |
|
||||
configDir = filepath.Join("/home", username, ".config", "Cursor", "User", "globalStorage") |
|
||||
default: |
|
||||
return "", fmt.Errorf("unsupported operating system: %s", runtime.GOOS) |
|
||||
} |
|
||||
return filepath.Join(configDir, "storage.json"), nil |
|
||||
} |
|
@ -1,187 +0,0 @@ |
|||||
package lang |
|
||||
|
|
||||
import ( |
|
||||
"os" |
|
||||
"os/exec" |
|
||||
"strings" |
|
||||
"sync" |
|
||||
) |
|
||||
|
|
||||
// Language represents a supported language code
|
|
||||
type Language string |
|
||||
|
|
||||
const ( |
|
||||
// CN represents Chinese language
|
|
||||
CN Language = "cn" |
|
||||
// EN represents English language
|
|
||||
EN Language = "en" |
|
||||
) |
|
||||
|
|
||||
// TextResource contains all translatable text resources
|
|
||||
type TextResource struct { |
|
||||
// Success messages
|
|
||||
SuccessMessage string |
|
||||
RestartMessage string |
|
||||
|
|
||||
// Progress messages
|
|
||||
ReadingConfig string |
|
||||
GeneratingIds string |
|
||||
CheckingProcesses string |
|
||||
ClosingProcesses string |
|
||||
ProcessesClosed string |
|
||||
PleaseWait string |
|
||||
|
|
||||
// Error messages
|
|
||||
ErrorPrefix string |
|
||||
PrivilegeError string |
|
||||
|
|
||||
// Instructions
|
|
||||
RunAsAdmin string |
|
||||
RunWithSudo string |
|
||||
SudoExample string |
|
||||
PressEnterToExit string |
|
||||
SetReadOnlyMessage string |
|
||||
|
|
||||
// Info messages
|
|
||||
ConfigLocation string |
|
||||
} |
|
||||
|
|
||||
var ( |
|
||||
currentLanguage Language |
|
||||
currentLanguageOnce sync.Once |
|
||||
languageMutex sync.RWMutex |
|
||||
) |
|
||||
|
|
||||
// GetCurrentLanguage returns the current language, detecting it if not already set
|
|
||||
func GetCurrentLanguage() Language { |
|
||||
currentLanguageOnce.Do(func() { |
|
||||
currentLanguage = detectLanguage() |
|
||||
}) |
|
||||
|
|
||||
languageMutex.RLock() |
|
||||
defer languageMutex.RUnlock() |
|
||||
return currentLanguage |
|
||||
} |
|
||||
|
|
||||
// SetLanguage sets the current language
|
|
||||
func SetLanguage(lang Language) { |
|
||||
languageMutex.Lock() |
|
||||
defer languageMutex.Unlock() |
|
||||
currentLanguage = lang |
|
||||
} |
|
||||
|
|
||||
// GetText returns the TextResource for the current language
|
|
||||
func GetText() TextResource { |
|
||||
return texts[GetCurrentLanguage()] |
|
||||
} |
|
||||
|
|
||||
// detectLanguage detects the system language
|
|
||||
func detectLanguage() Language { |
|
||||
// Check environment variables first
|
|
||||
if isChineseEnvVar() { |
|
||||
return CN |
|
||||
} |
|
||||
|
|
||||
// Then check OS-specific locale
|
|
||||
if isWindows() { |
|
||||
if isWindowsChineseLocale() { |
|
||||
return CN |
|
||||
} |
|
||||
} else if isUnixChineseLocale() { |
|
||||
return CN |
|
||||
} |
|
||||
|
|
||||
return EN |
|
||||
} |
|
||||
|
|
||||
func isChineseEnvVar() bool { |
|
||||
for _, envVar := range []string{"LANG", "LANGUAGE", "LC_ALL"} { |
|
||||
if lang := os.Getenv(envVar); lang != "" && strings.Contains(strings.ToLower(lang), "zh") { |
|
||||
return true |
|
||||
} |
|
||||
} |
|
||||
return false |
|
||||
} |
|
||||
|
|
||||
func isWindows() bool { |
|
||||
return os.Getenv("OS") == "Windows_NT" |
|
||||
} |
|
||||
|
|
||||
func isWindowsChineseLocale() bool { |
|
||||
// Check Windows UI culture
|
|
||||
cmd := exec.Command("powershell", "-Command", |
|
||||
"[System.Globalization.CultureInfo]::CurrentUICulture.Name") |
|
||||
output, err := cmd.Output() |
|
||||
if err == nil && strings.HasPrefix(strings.ToLower(strings.TrimSpace(string(output))), "zh") { |
|
||||
return true |
|
||||
} |
|
||||
|
|
||||
// Check Windows locale
|
|
||||
cmd = exec.Command("wmic", "os", "get", "locale") |
|
||||
output, err = cmd.Output() |
|
||||
return err == nil && strings.Contains(string(output), "2052") |
|
||||
} |
|
||||
|
|
||||
func isUnixChineseLocale() bool { |
|
||||
cmd := exec.Command("locale") |
|
||||
output, err := cmd.Output() |
|
||||
return err == nil && strings.Contains(strings.ToLower(string(output)), "zh_cn") |
|
||||
} |
|
||||
|
|
||||
// texts contains all translations
|
|
||||
var texts = map[Language]TextResource{ |
|
||||
CN: { |
|
||||
// Success messages
|
|
||||
SuccessMessage: "[√] 配置文件已成功更新!", |
|
||||
RestartMessage: "[!] 请手动重启 Cursor 以使更新生效", |
|
||||
|
|
||||
// Progress messages
|
|
||||
ReadingConfig: "正在读取配置文件...", |
|
||||
GeneratingIds: "正在生成新的标识符...", |
|
||||
CheckingProcesses: "正在检查运行中的 Cursor 实例...", |
|
||||
ClosingProcesses: "正在关闭 Cursor 实例...", |
|
||||
ProcessesClosed: "所有 Cursor 实例已关闭", |
|
||||
PleaseWait: "请稍候...", |
|
||||
|
|
||||
// Error messages
|
|
||||
ErrorPrefix: "程序发生严重错误: %v", |
|
||||
PrivilegeError: "\n[!] 错误:需要管理员权限", |
|
||||
|
|
||||
// Instructions
|
|
||||
RunAsAdmin: "请右键点击程序,选择「以管理员身份运行」", |
|
||||
RunWithSudo: "请使用 sudo 命令运行此程序", |
|
||||
SudoExample: "示例: sudo %s", |
|
||||
PressEnterToExit: "\n按回车键退出程序...", |
|
||||
SetReadOnlyMessage: "设置 storage.json 为只读模式, 这将导致 workspace 记录信息丢失等问题", |
|
||||
|
|
||||
// Info messages
|
|
||||
ConfigLocation: "配置文件位置:", |
|
||||
}, |
|
||||
EN: { |
|
||||
// Success messages
|
|
||||
SuccessMessage: "[√] Configuration file updated successfully!", |
|
||||
RestartMessage: "[!] Please restart Cursor manually for changes to take effect", |
|
||||
|
|
||||
// Progress messages
|
|
||||
ReadingConfig: "Reading configuration file...", |
|
||||
GeneratingIds: "Generating new identifiers...", |
|
||||
CheckingProcesses: "Checking for running Cursor instances...", |
|
||||
ClosingProcesses: "Closing Cursor instances...", |
|
||||
ProcessesClosed: "All Cursor instances have been closed", |
|
||||
PleaseWait: "Please wait...", |
|
||||
|
|
||||
// Error messages
|
|
||||
ErrorPrefix: "Program encountered a serious error: %v", |
|
||||
PrivilegeError: "\n[!] Error: Administrator privileges required", |
|
||||
|
|
||||
// Instructions
|
|
||||
RunAsAdmin: "Please right-click and select 'Run as Administrator'", |
|
||||
RunWithSudo: "Please run this program with sudo", |
|
||||
SudoExample: "Example: sudo %s", |
|
||||
PressEnterToExit: "\nPress Enter to exit...", |
|
||||
SetReadOnlyMessage: "Set storage.json to read-only mode, which will cause issues such as lost workspace records", |
|
||||
|
|
||||
// Info messages
|
|
||||
ConfigLocation: "Config file location:", |
|
||||
}, |
|
||||
} |
|
@ -1,216 +0,0 @@ |
|||||
package process |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"os/exec" |
|
||||
"runtime" |
|
||||
"strings" |
|
||||
"time" |
|
||||
|
|
||||
"github.com/sirupsen/logrus" |
|
||||
) |
|
||||
|
|
||||
// Config holds process manager configuration
|
|
||||
type Config struct { |
|
||||
MaxAttempts int // Maximum number of attempts to kill processes
|
|
||||
RetryDelay time.Duration // Delay between retry attempts
|
|
||||
ProcessPatterns []string // Process names to look for
|
|
||||
} |
|
||||
|
|
||||
// DefaultConfig returns the default configuration
|
|
||||
func DefaultConfig() *Config { |
|
||||
return &Config{ |
|
||||
MaxAttempts: 3, |
|
||||
RetryDelay: 2 * time.Second, |
|
||||
ProcessPatterns: []string{ |
|
||||
"Cursor.exe", // Windows executable
|
|
||||
"Cursor ", // Linux/macOS executable with space
|
|
||||
"cursor ", // Linux/macOS executable lowercase with space
|
|
||||
"cursor", // Linux/macOS executable lowercase
|
|
||||
"Cursor", // Linux/macOS executable
|
|
||||
"*cursor*", // Any process containing cursor
|
|
||||
"*Cursor*", // Any process containing Cursor
|
|
||||
}, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Manager handles process-related operations
|
|
||||
type Manager struct { |
|
||||
config *Config |
|
||||
log *logrus.Logger |
|
||||
} |
|
||||
|
|
||||
// NewManager creates a new process manager with optional config and logger
|
|
||||
func NewManager(config *Config, log *logrus.Logger) *Manager { |
|
||||
if config == nil { |
|
||||
config = DefaultConfig() |
|
||||
} |
|
||||
if log == nil { |
|
||||
log = logrus.New() |
|
||||
} |
|
||||
return &Manager{ |
|
||||
config: config, |
|
||||
log: log, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// IsCursorRunning checks if any Cursor process is currently running
|
|
||||
func (m *Manager) IsCursorRunning() bool { |
|
||||
processes, err := m.getCursorProcesses() |
|
||||
if err != nil { |
|
||||
m.log.Warn("Failed to get Cursor processes:", err) |
|
||||
return false |
|
||||
} |
|
||||
return len(processes) > 0 |
|
||||
} |
|
||||
|
|
||||
// KillCursorProcesses attempts to kill all running Cursor processes
|
|
||||
func (m *Manager) KillCursorProcesses() error { |
|
||||
for attempt := 1; attempt <= m.config.MaxAttempts; attempt++ { |
|
||||
processes, err := m.getCursorProcesses() |
|
||||
if err != nil { |
|
||||
return fmt.Errorf("failed to get processes: %w", err) |
|
||||
} |
|
||||
|
|
||||
if len(processes) == 0 { |
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// Try graceful shutdown first on Windows
|
|
||||
if runtime.GOOS == "windows" { |
|
||||
for _, pid := range processes { |
|
||||
exec.Command("taskkill", "/PID", pid).Run() |
|
||||
time.Sleep(500 * time.Millisecond) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Force kill remaining processes
|
|
||||
remainingProcesses, _ := m.getCursorProcesses() |
|
||||
for _, pid := range remainingProcesses { |
|
||||
m.killProcess(pid) |
|
||||
} |
|
||||
|
|
||||
time.Sleep(m.config.RetryDelay) |
|
||||
|
|
||||
if processes, _ := m.getCursorProcesses(); len(processes) == 0 { |
|
||||
return nil |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
return nil |
|
||||
} |
|
||||
|
|
||||
// getCursorProcesses returns PIDs of running Cursor processes
|
|
||||
func (m *Manager) getCursorProcesses() ([]string, error) { |
|
||||
cmd := m.getProcessListCommand() |
|
||||
if cmd == nil { |
|
||||
return nil, fmt.Errorf("unsupported operating system: %s", runtime.GOOS) |
|
||||
} |
|
||||
|
|
||||
output, err := cmd.Output() |
|
||||
if err != nil { |
|
||||
return nil, fmt.Errorf("failed to execute command: %w", err) |
|
||||
} |
|
||||
|
|
||||
return m.parseProcessList(string(output)), nil |
|
||||
} |
|
||||
|
|
||||
// getProcessListCommand returns the appropriate command to list processes based on OS
|
|
||||
func (m *Manager) getProcessListCommand() *exec.Cmd { |
|
||||
switch runtime.GOOS { |
|
||||
case "windows": |
|
||||
return exec.Command("tasklist", "/FO", "CSV", "/NH") |
|
||||
case "darwin": |
|
||||
return exec.Command("ps", "-ax") |
|
||||
case "linux": |
|
||||
return exec.Command("ps", "-A") |
|
||||
default: |
|
||||
return nil |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// parseProcessList extracts Cursor process PIDs from process list output
|
|
||||
func (m *Manager) parseProcessList(output string) []string { |
|
||||
var processes []string |
|
||||
for _, line := range strings.Split(output, "\n") { |
|
||||
lowerLine := strings.ToLower(line) |
|
||||
|
|
||||
if m.isOwnProcess(lowerLine) { |
|
||||
continue |
|
||||
} |
|
||||
|
|
||||
if pid := m.findCursorProcess(line, lowerLine); pid != "" { |
|
||||
processes = append(processes, pid) |
|
||||
} |
|
||||
} |
|
||||
return processes |
|
||||
} |
|
||||
|
|
||||
// isOwnProcess checks if the process belongs to this application
|
|
||||
func (m *Manager) isOwnProcess(line string) bool { |
|
||||
return strings.Contains(line, "cursor-id-modifier") || |
|
||||
strings.Contains(line, "cursor-helper") |
|
||||
} |
|
||||
|
|
||||
// findCursorProcess checks if a process line matches Cursor patterns and returns its PID
|
|
||||
func (m *Manager) findCursorProcess(line, lowerLine string) string { |
|
||||
for _, pattern := range m.config.ProcessPatterns { |
|
||||
if m.matchPattern(lowerLine, strings.ToLower(pattern)) { |
|
||||
return m.extractPID(line) |
|
||||
} |
|
||||
} |
|
||||
return "" |
|
||||
} |
|
||||
|
|
||||
// matchPattern checks if a line matches a pattern, supporting wildcards
|
|
||||
func (m *Manager) matchPattern(line, pattern string) bool { |
|
||||
switch { |
|
||||
case strings.HasPrefix(pattern, "*") && strings.HasSuffix(pattern, "*"): |
|
||||
search := pattern[1 : len(pattern)-1] |
|
||||
return strings.Contains(line, search) |
|
||||
case strings.HasPrefix(pattern, "*"): |
|
||||
return strings.HasSuffix(line, pattern[1:]) |
|
||||
case strings.HasSuffix(pattern, "*"): |
|
||||
return strings.HasPrefix(line, pattern[:len(pattern)-1]) |
|
||||
default: |
|
||||
return line == pattern |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// extractPID extracts process ID from a process list line based on OS format
|
|
||||
func (m *Manager) extractPID(line string) string { |
|
||||
switch runtime.GOOS { |
|
||||
case "windows": |
|
||||
parts := strings.Split(line, ",") |
|
||||
if len(parts) >= 2 { |
|
||||
return strings.Trim(parts[1], "\"") |
|
||||
} |
|
||||
case "darwin", "linux": |
|
||||
parts := strings.Fields(line) |
|
||||
if len(parts) >= 1 { |
|
||||
return parts[0] |
|
||||
} |
|
||||
} |
|
||||
return "" |
|
||||
} |
|
||||
|
|
||||
// killProcess forcefully terminates a process by PID
|
|
||||
func (m *Manager) killProcess(pid string) error { |
|
||||
cmd := m.getKillCommand(pid) |
|
||||
if cmd == nil { |
|
||||
return fmt.Errorf("unsupported operating system: %s", runtime.GOOS) |
|
||||
} |
|
||||
return cmd.Run() |
|
||||
} |
|
||||
|
|
||||
// getKillCommand returns the appropriate command to kill a process based on OS
|
|
||||
func (m *Manager) getKillCommand(pid string) *exec.Cmd { |
|
||||
switch runtime.GOOS { |
|
||||
case "windows": |
|
||||
return exec.Command("taskkill", "/F", "/PID", pid) |
|
||||
case "darwin", "linux": |
|
||||
return exec.Command("kill", "-9", pid) |
|
||||
default: |
|
||||
return nil |
|
||||
} |
|
||||
} |
|
@ -1,94 +0,0 @@ |
|||||
package ui |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"os" |
|
||||
"os/exec" |
|
||||
"runtime" |
|
||||
"strings" |
|
||||
|
|
||||
"github.com/fatih/color" |
|
||||
) |
|
||||
|
|
||||
// Display handles UI operations for terminal output
|
|
||||
type Display struct { |
|
||||
spinner *Spinner |
|
||||
} |
|
||||
|
|
||||
// NewDisplay creates a new display instance with an optional spinner
|
|
||||
func NewDisplay(spinner *Spinner) *Display { |
|
||||
if spinner == nil { |
|
||||
spinner = NewSpinner(nil) |
|
||||
} |
|
||||
return &Display{spinner: spinner} |
|
||||
} |
|
||||
|
|
||||
// Terminal Operations
|
|
||||
|
|
||||
// ClearScreen clears the terminal screen based on OS
|
|
||||
func (d *Display) ClearScreen() error { |
|
||||
var cmd *exec.Cmd |
|
||||
switch runtime.GOOS { |
|
||||
case "windows": |
|
||||
cmd = exec.Command("cmd", "/c", "cls") |
|
||||
default: |
|
||||
cmd = exec.Command("clear") |
|
||||
} |
|
||||
cmd.Stdout = os.Stdout |
|
||||
return cmd.Run() |
|
||||
} |
|
||||
|
|
||||
// Progress Indicator
|
|
||||
|
|
||||
// ShowProgress displays a progress message with a spinner
|
|
||||
func (d *Display) ShowProgress(message string) { |
|
||||
d.spinner.SetMessage(message) |
|
||||
d.spinner.Start() |
|
||||
} |
|
||||
|
|
||||
// StopProgress stops the progress spinner
|
|
||||
func (d *Display) StopProgress() { |
|
||||
d.spinner.Stop() |
|
||||
} |
|
||||
|
|
||||
// Message Display
|
|
||||
|
|
||||
// ShowSuccess displays success messages in green
|
|
||||
func (d *Display) ShowSuccess(messages ...string) { |
|
||||
green := color.New(color.FgGreen) |
|
||||
for _, msg := range messages { |
|
||||
green.Println(msg) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// ShowInfo displays an info message in cyan
|
|
||||
func (d *Display) ShowInfo(message string) { |
|
||||
cyan := color.New(color.FgCyan) |
|
||||
cyan.Println(message) |
|
||||
} |
|
||||
|
|
||||
// ShowError displays an error message in red
|
|
||||
func (d *Display) ShowError(message string) { |
|
||||
red := color.New(color.FgRed) |
|
||||
red.Println(message) |
|
||||
} |
|
||||
|
|
||||
// ShowPrivilegeError displays privilege error messages with instructions
|
|
||||
func (d *Display) ShowPrivilegeError(messages ...string) { |
|
||||
red := color.New(color.FgRed, color.Bold) |
|
||||
yellow := color.New(color.FgYellow) |
|
||||
|
|
||||
// Main error message
|
|
||||
red.Println(messages[0]) |
|
||||
fmt.Println() |
|
||||
|
|
||||
// Additional instructions
|
|
||||
for _, msg := range messages[1:] { |
|
||||
if strings.Contains(msg, "%s") { |
|
||||
exe, _ := os.Executable() |
|
||||
yellow.Printf(msg+"\n", exe) |
|
||||
} else { |
|
||||
yellow.Println(msg) |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,20 +0,0 @@ |
|||||
package ui |
|
||||
|
|
||||
import ( |
|
||||
"github.com/fatih/color" |
|
||||
) |
|
||||
|
|
||||
const cyberpunkLogo = ` |
|
||||
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ |
|
||||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ |
|
||||
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ |
|
||||
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ |
|
||||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ |
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ |
|
||||
` |
|
||||
|
|
||||
// ShowLogo displays the application logo
|
|
||||
func (d *Display) ShowLogo() { |
|
||||
cyan := color.New(color.FgCyan, color.Bold) |
|
||||
cyan.Println(cyberpunkLogo) |
|
||||
} |
|
@ -1,122 +0,0 @@ |
|||||
package ui |
|
||||
|
|
||||
import ( |
|
||||
"fmt" |
|
||||
"sync" |
|
||||
"time" |
|
||||
|
|
||||
"github.com/fatih/color" |
|
||||
) |
|
||||
|
|
||||
// SpinnerConfig defines spinner configuration
|
|
||||
type SpinnerConfig struct { |
|
||||
Frames []string // Animation frames for the spinner
|
|
||||
Delay time.Duration // Delay between frame updates
|
|
||||
} |
|
||||
|
|
||||
// DefaultSpinnerConfig returns the default spinner configuration
|
|
||||
func DefaultSpinnerConfig() *SpinnerConfig { |
|
||||
return &SpinnerConfig{ |
|
||||
Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, |
|
||||
Delay: 100 * time.Millisecond, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Spinner represents a progress spinner
|
|
||||
type Spinner struct { |
|
||||
config *SpinnerConfig |
|
||||
message string |
|
||||
current int |
|
||||
active bool |
|
||||
stopCh chan struct{} |
|
||||
mu sync.RWMutex |
|
||||
} |
|
||||
|
|
||||
// NewSpinner creates a new spinner with the given configuration
|
|
||||
func NewSpinner(config *SpinnerConfig) *Spinner { |
|
||||
if config == nil { |
|
||||
config = DefaultSpinnerConfig() |
|
||||
} |
|
||||
return &Spinner{ |
|
||||
config: config, |
|
||||
stopCh: make(chan struct{}), |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// State management
|
|
||||
|
|
||||
// SetMessage sets the spinner message
|
|
||||
func (s *Spinner) SetMessage(message string) { |
|
||||
s.mu.Lock() |
|
||||
defer s.mu.Unlock() |
|
||||
s.message = message |
|
||||
} |
|
||||
|
|
||||
// IsActive returns whether the spinner is currently active
|
|
||||
func (s *Spinner) IsActive() bool { |
|
||||
s.mu.RLock() |
|
||||
defer s.mu.RUnlock() |
|
||||
return s.active |
|
||||
} |
|
||||
|
|
||||
// Control methods
|
|
||||
|
|
||||
// Start begins the spinner animation
|
|
||||
func (s *Spinner) Start() { |
|
||||
s.mu.Lock() |
|
||||
if s.active { |
|
||||
s.mu.Unlock() |
|
||||
return |
|
||||
} |
|
||||
s.active = true |
|
||||
s.mu.Unlock() |
|
||||
|
|
||||
go s.run() |
|
||||
} |
|
||||
|
|
||||
// Stop halts the spinner animation
|
|
||||
func (s *Spinner) Stop() { |
|
||||
s.mu.Lock() |
|
||||
defer s.mu.Unlock() |
|
||||
|
|
||||
if !s.active { |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
s.active = false |
|
||||
close(s.stopCh) |
|
||||
s.stopCh = make(chan struct{}) |
|
||||
fmt.Print("\r") // Clear the spinner line
|
|
||||
} |
|
||||
|
|
||||
// Internal methods
|
|
||||
|
|
||||
func (s *Spinner) run() { |
|
||||
ticker := time.NewTicker(s.config.Delay) |
|
||||
defer ticker.Stop() |
|
||||
|
|
||||
cyan := color.New(color.FgCyan, color.Bold) |
|
||||
message := s.message |
|
||||
|
|
||||
// Print initial state
|
|
||||
fmt.Printf("\r %s %s", cyan.Sprint(s.config.Frames[0]), message) |
|
||||
|
|
||||
for { |
|
||||
select { |
|
||||
case <-s.stopCh: |
|
||||
return |
|
||||
case <-ticker.C: |
|
||||
s.mu.RLock() |
|
||||
if !s.active { |
|
||||
s.mu.RUnlock() |
|
||||
return |
|
||||
} |
|
||||
frame := s.config.Frames[s.current%len(s.config.Frames)] |
|
||||
s.current++ |
|
||||
s.mu.RUnlock() |
|
||||
|
|
||||
fmt.Printf("\r %s", cyan.Sprint(frame)) |
|
||||
fmt.Printf("\033[%dG%s", 4, message) // Move cursor and print message
|
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -1,116 +0,0 @@ |
|||||
package idgen |
|
||||
|
|
||||
import ( |
|
||||
"crypto/rand" |
|
||||
"encoding/hex" |
|
||||
"fmt" |
|
||||
"sync" |
|
||||
) |
|
||||
|
|
||||
// Generator handles secure ID generation for machines and devices
|
|
||||
type Generator struct { |
|
||||
bufferPool sync.Pool |
|
||||
} |
|
||||
|
|
||||
// NewGenerator creates a new ID generator
|
|
||||
func NewGenerator() *Generator { |
|
||||
return &Generator{ |
|
||||
bufferPool: sync.Pool{ |
|
||||
New: func() interface{} { |
|
||||
return make([]byte, 64) |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Constants for ID generation
|
|
||||
const ( |
|
||||
machineIDPrefix = "auth0|user_" |
|
||||
uuidFormat = "%s-%s-%s-%s-%s" |
|
||||
) |
|
||||
|
|
||||
// generateRandomHex generates a random hex string of specified length
|
|
||||
func (g *Generator) generateRandomHex(length int) (string, error) { |
|
||||
buffer := g.bufferPool.Get().([]byte) |
|
||||
defer g.bufferPool.Put(buffer) |
|
||||
|
|
||||
if _, err := rand.Read(buffer[:length]); err != nil { |
|
||||
return "", fmt.Errorf("failed to generate random bytes: %w", err) |
|
||||
} |
|
||||
return hex.EncodeToString(buffer[:length]), nil |
|
||||
} |
|
||||
|
|
||||
// GenerateMachineID generates a new machine ID with auth0|user_ prefix
|
|
||||
func (g *Generator) GenerateMachineID() (string, error) { |
|
||||
randomPart, err := g.generateRandomHex(32) // 生成64字符的十六进制
|
|
||||
if err != nil { |
|
||||
return "", err |
|
||||
} |
|
||||
|
|
||||
return fmt.Sprintf("%x%s", []byte(machineIDPrefix), randomPart), nil |
|
||||
} |
|
||||
|
|
||||
// GenerateMacMachineID generates a new 64-byte MAC machine ID
|
|
||||
func (g *Generator) GenerateMacMachineID() (string, error) { |
|
||||
return g.generateRandomHex(32) // 生成64字符的十六进制
|
|
||||
} |
|
||||
|
|
||||
// GenerateDeviceID generates a new device ID in UUID format
|
|
||||
func (g *Generator) GenerateDeviceID() (string, error) { |
|
||||
id, err := g.generateRandomHex(16) |
|
||||
if err != nil { |
|
||||
return "", err |
|
||||
} |
|
||||
return fmt.Sprintf(uuidFormat, |
|
||||
id[0:8], id[8:12], id[12:16], id[16:20], id[20:32]), nil |
|
||||
} |
|
||||
|
|
||||
// GenerateSQMID generates a new SQM ID in UUID format (with braces)
|
|
||||
func (g *Generator) GenerateSQMID() (string, error) { |
|
||||
id, err := g.GenerateDeviceID() |
|
||||
if err != nil { |
|
||||
return "", err |
|
||||
} |
|
||||
return fmt.Sprintf("{%s}", id), nil |
|
||||
} |
|
||||
|
|
||||
// ValidateID validates the format of various ID types
|
|
||||
func (g *Generator) ValidateID(id string, idType string) bool { |
|
||||
switch idType { |
|
||||
case "machineID", "macMachineID": |
|
||||
return len(id) == 64 && isHexString(id) |
|
||||
case "deviceID": |
|
||||
return isValidUUID(id) |
|
||||
case "sqmID": |
|
||||
if len(id) < 2 || id[0] != '{' || id[len(id)-1] != '}' { |
|
||||
return false |
|
||||
} |
|
||||
return isValidUUID(id[1 : len(id)-1]) |
|
||||
default: |
|
||||
return false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Helper functions
|
|
||||
func isHexString(s string) bool { |
|
||||
_, err := hex.DecodeString(s) |
|
||||
return err == nil |
|
||||
} |
|
||||
|
|
||||
func isValidUUID(uuid string) bool { |
|
||||
if len(uuid) != 36 { |
|
||||
return false |
|
||||
} |
|
||||
for i, r := range uuid { |
|
||||
if i == 8 || i == 13 || i == 18 || i == 23 { |
|
||||
if r != '-' { |
|
||||
return false |
|
||||
} |
|
||||
continue |
|
||||
} |
|
||||
if !((r >= '0' && r <= '9') || (r >= 'a' && r <= 'f') || (r >= 'A' && r <= 'F')) { |
|
||||
return false |
|
||||
} |
|
||||
} |
|
||||
return true |
|
||||
} |
|
@ -1,375 +0,0 @@ |
|||||
import csv |
|
||||
from dataclasses import dataclass |
|
||||
from typing import List |
|
||||
import json |
|
||||
|
|
||||
@dataclass |
|
||||
class CursorVersion: |
|
||||
version: str |
|
||||
build_id: str |
|
||||
|
|
||||
def get_download_links(self) -> dict: |
|
||||
base_url = f"https://downloader.cursor.sh/builds/{self.build_id}" |
|
||||
return { |
|
||||
"windows": { |
|
||||
"x64": f"{base_url}/windows/nsis/x64", |
|
||||
"arm64": f"{base_url}/windows/nsis/arm64" |
|
||||
}, |
|
||||
"mac": { |
|
||||
"universal": f"{base_url}/mac/installer/universal", |
|
||||
"arm64": f"{base_url}/mac/installer/arm64", |
|
||||
"x64": f"{base_url}/mac/installer/x64" |
|
||||
}, |
|
||||
"linux": { |
|
||||
"x64": f"{base_url}/linux/appImage/x64" |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
def parse_versions(data: str) -> List[CursorVersion]: |
|
||||
versions = [] |
|
||||
for line in data.strip().split('\n'): |
|
||||
if not line: |
|
||||
continue |
|
||||
version, build_id = line.strip().split(',') |
|
||||
versions.append(CursorVersion(version, build_id)) |
|
||||
return versions |
|
||||
|
|
||||
def generate_markdown(versions: List[CursorVersion]) -> str: |
|
||||
md = """# 🖥️ Windows |
|
||||
|
|
||||
## x64 |
|
||||
<details> |
|
||||
<summary style="font-size:1.2em">📦 Windows x64 安装包</summary> |
|
||||
|
|
||||
| 版本 | 下载链接 | |
|
||||
|------|----------| |
|
||||
""" |
|
||||
|
|
||||
# Windows x64 |
|
||||
for version in versions: |
|
||||
links = version.get_download_links() |
|
||||
md += f"| {version.version} | [下载]({links['windows']['x64']}) |\n" |
|
||||
|
|
||||
md += """ |
|
||||
</details> |
|
||||
|
|
||||
## ARM64 |
|
||||
<details> |
|
||||
<summary style="font-size:1.2em">📱 Windows ARM64 安装包</summary> |
|
||||
|
|
||||
| 版本 | 下载链接 | |
|
||||
|------|----------| |
|
||||
""" |
|
||||
|
|
||||
# Windows ARM64 |
|
||||
for version in versions: |
|
||||
links = version.get_download_links() |
|
||||
md += f"| {version.version} | [下载]({links['windows']['arm64']}) |\n" |
|
||||
|
|
||||
md += """ |
|
||||
</details> |
|
||||
|
|
||||
# 🍎 macOS |
|
||||
|
|
||||
## Universal |
|
||||
<details> |
|
||||
<summary style="font-size:1.2em">🎯 macOS Universal 安装包</summary> |
|
||||
|
|
||||
| 版本 | 下载链接 | |
|
||||
|------|----------| |
|
||||
""" |
|
||||
|
|
||||
# macOS Universal |
|
||||
for version in versions: |
|
||||
links = version.get_download_links() |
|
||||
md += f"| {version.version} | [下载]({links['mac']['universal']}) |\n" |
|
||||
|
|
||||
md += """ |
|
||||
</details> |
|
||||
|
|
||||
## ARM64 |
|
||||
<details> |
|
||||
<summary style="font-size:1.2em">💪 macOS ARM64 安装包</summary> |
|
||||
|
|
||||
| 版本 | 下载链接 | |
|
||||
|------|----------| |
|
||||
""" |
|
||||
|
|
||||
# macOS ARM64 |
|
||||
for version in versions: |
|
||||
links = version.get_download_links() |
|
||||
md += f"| {version.version} | [下载]({links['mac']['arm64']}) |\n" |
|
||||
|
|
||||
md += """ |
|
||||
</details> |
|
||||
|
|
||||
## Intel |
|
||||
<details> |
|
||||
<summary style="font-size:1.2em">💻 macOS Intel 安装包</summary> |
|
||||
|
|
||||
| 版本 | 下载链接 | |
|
||||
|------|----------| |
|
||||
""" |
|
||||
|
|
||||
# macOS Intel |
|
||||
for version in versions: |
|
||||
links = version.get_download_links() |
|
||||
md += f"| {version.version} | [下载]({links['mac']['x64']}) |\n" |
|
||||
|
|
||||
md += """ |
|
||||
</details> |
|
||||
|
|
||||
# 🐧 Linux |
|
||||
|
|
||||
## x64 |
|
||||
<details> |
|
||||
<summary style="font-size:1.2em">🎮 Linux x64 AppImage</summary> |
|
||||
|
|
||||
| 版本 | 下载链接 | |
|
||||
|------|----------| |
|
||||
""" |
|
||||
|
|
||||
# Linux x64 |
|
||||
for version in versions: |
|
||||
links = version.get_download_links() |
|
||||
md += f"| {version.version} | [下载]({links['linux']['x64']}) |\n" |
|
||||
|
|
||||
md += """ |
|
||||
</details> |
|
||||
|
|
||||
<style> |
|
||||
details { |
|
||||
margin: 1em 0; |
|
||||
padding: 0.5em 1em; |
|
||||
background: #f8f9fa; |
|
||||
border-radius: 8px; |
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1); |
|
||||
} |
|
||||
|
|
||||
summary { |
|
||||
cursor: pointer; |
|
||||
font-weight: bold; |
|
||||
margin: -0.5em -1em; |
|
||||
padding: 0.5em 1em; |
|
||||
} |
|
||||
|
|
||||
summary:hover { |
|
||||
background: #f1f3f5; |
|
||||
} |
|
||||
|
|
||||
table { |
|
||||
width: 100%; |
|
||||
border-collapse: collapse; |
|
||||
margin-top: 1em; |
|
||||
} |
|
||||
|
|
||||
th, td { |
|
||||
padding: 0.5em; |
|
||||
text-align: left; |
|
||||
border-bottom: 1px solid #dee2e6; |
|
||||
} |
|
||||
|
|
||||
tr:hover { |
|
||||
background: #f1f3f5; |
|
||||
} |
|
||||
|
|
||||
a { |
|
||||
color: #0366d6; |
|
||||
text-decoration: none; |
|
||||
} |
|
||||
|
|
||||
a:hover { |
|
||||
text-decoration: underline; |
|
||||
} |
|
||||
</style> |
|
||||
""" |
|
||||
return md |
|
||||
|
|
||||
def main(): |
|
||||
# 示例数据 |
|
||||
data = """ |
|
||||
0.45.11,250207y6nbaw5qc |
|
||||
0.45.10,250205buadkzpea |
|
||||
0.45.9,250202tgstl42dt |
|
||||
0.45.8,250201b44xw1x2k |
|
||||
0.45.7,250130nr6eorv84 |
|
||||
0.45.6,25013021lv9say3 |
|
||||
0.45.5,250128loaeyulq8 |
|
||||
0.45.4,250126vgr3vztvj |
|
||||
0.45.3,250124b0rcj0qql |
|
||||
0.45.2,250123mhituoa6o |
|
||||
0.45.1,2501213ljml5byg |
|
||||
0.45.0,250120dh9ezx9pg |
|
||||
0.44.11,250103fqxdt5u9z |
|
||||
0.44.10,250102ys80vtnud |
|
||||
0.44.9,2412268nc6pfzgo |
|
||||
0.44.8,241222ooktny8mh |
|
||||
0.44.7,2412219nhracv01 |
|
||||
0.44.6,2412214pmryneua |
|
||||
0.44.5,241220s3ux0e1tv |
|
||||
0.44.4,241219117fcvexy |
|
||||
0.44.3,241218sybfbogmq |
|
||||
0.44.2,241218ntls52u8v |
|
||||
0.44.0,2412187f9v0nffu |
|
||||
0.43.6,241206z7j6me2e2 |
|
||||
0.43.5,241127pdg4cnbu2 |
|
||||
0.43.4,241126w13goyvrs |
|
||||
0.43.3,2411246yqzx1jmm |
|
||||
0.43.1,241124gsiwb66nc |
|
||||
0.42.5,24111460bf2loz1 |
|
||||
0.42.4,2410291z3bdg1dy |
|
||||
0.42.3,241016kxu9umuir |
|
||||
0.42.2,2410127mj66lvaq |
|
||||
0.42.1,241011i66p9fuvm |
|
||||
0.42.0,241009fij7nohn5 |
|
||||
0.41.3,240925fkhcqg263 |
|
||||
0.41.2,240921llnho65ov |
|
||||
0.41.1,2409189xe3envg5 |
|
||||
0.40.4,2409052yfcjagw2 |
|
||||
0.40.3,240829epqamqp7h |
|
||||
0.40.2,240828c021k3aib |
|
||||
0.40.1,2408245thnycuzj |
|
||||
0.40.0,24082202sreugb2 |
|
||||
0.39.6,240819ih4ta2fye |
|
||||
0.39.5,240814y9rhzmu7h |
|
||||
0.39.4,240810elmeg3seq |
|
||||
0.39.3,2408092hoyaxt9m |
|
||||
0.39.2,240808phaxh4b5r |
|
||||
0.39.1,240807g919tr4ly |
|
||||
0.39.0,240802cdixtv9a6 |
|
||||
0.38.1,240725f0ti25os7 |
|
||||
0.38.0,240723790oxe4a2 |
|
||||
0.37.1,240714yrr3gmv3k |
|
||||
0.36.2,2407077n6pzboby |
|
||||
0.36.1,240706uekt2eaft |
|
||||
0.36.0,240703xqkjv5aqa |
|
||||
0.35.1,240621pc2f7rl8a |
|
||||
0.35.0,240608cv11mfsjl |
|
||||
0.34.6,240606kgzq24cfb |
|
||||
0.34.6,240605r495newcf |
|
||||
0.34.5,240602rq6xovt3a |
|
||||
0.34.4,2406014h0rgjghe |
|
||||
0.34.3,240529baisuyd2e |
|
||||
0.34.2,240528whh1qyo9h |
|
||||
0.34.1,24052838ygfselt |
|
||||
0.34.0,240527xus72jmkj |
|
||||
0.33.4,240511kb8wt1tms |
|
||||
0.33.3,2405103lx8342ta |
|
||||
0.33.2,240510dwmw395qe |
|
||||
0.33.1,2405039a9h2fqc9 |
|
||||
0.33.0,240503hyjsnhazo |
|
||||
0.32.8,240428d499o6zja |
|
||||
0.32.7,240427w5guozr0l |
|
||||
0.32.2,240417ab4wag7sx |
|
||||
0.32.1,2404152czor73fk |
|
||||
0.32.0,240412ugli06ue0 |
|
||||
0.31.3,240402rq154jw46 |
|
||||
0.31.1,240402pkwfm2ps6 |
|
||||
0.31.0,2404018j7z0xv2g |
|
||||
0.30.5,240327tmd2ozdc7 |
|
||||
0.30.4,240325dezy8ziab |
|
||||
0.30.3,2403229gtuhto9g |
|
||||
0.30.2,240322gzqjm3p0d |
|
||||
0.30.1,2403212w1ejubt8 |
|
||||
0.30.0,240320tpx86e7hk |
|
||||
0.29.1,2403027twmz0d1t |
|
||||
0.29.0,240301kpqvacw2h |
|
||||
0.28.1,240226tstim4evd |
|
||||
0.28.0,240224g2d7jazcq |
|
||||
0.27.4,240219qdbagglqz |
|
||||
0.27.3,240218dxhc6y8os |
|
||||
0.27.2,240216kkzl9nhxi |
|
||||
0.27.1,240215l4ooehnyl |
|
||||
0.27.0,240215at6ewkd59 |
|
||||
0.26.2,240212o6r9qxtcg |
|
||||
0.26.1,2402107t904hing |
|
||||
0.26.0,240210k8is5xr6v |
|
||||
0.25.3,240207aacboj1k8 |
|
||||
0.25.2,240206p3708uc9z |
|
||||
0.25.1,2402033t030rprh |
|
||||
0.25.0,240203kh86t91q8 |
|
||||
0.24.4,240129iecm3e33w |
|
||||
0.24.3,2401289dx79qsc0 |
|
||||
0.24.1,240127cad17436d |
|
||||
0.24.0,240126wp9irhmza |
|
||||
0.23.9,240124dsmraeml3 |
|
||||
0.23.8,240123fnn1hj1fg |
|
||||
0.23.7,240123xsfe7ywcv |
|
||||
0.23.6,240121m1740elox |
|
||||
0.23.5,2401215utj6tx6q |
|
||||
0.23.4,240121f4qy6ba2y |
|
||||
0.23.3,2401201und3ytom |
|
||||
0.23.2,240120an2k2hf1i |
|
||||
0.23.1,240119fgzxwudn9 |
|
||||
0.22.2,24011721vsch1l1 |
|
||||
0.22.1,2401083eyk8kmzc |
|
||||
0.22.0,240107qk62kvva3 |
|
||||
0.21.1,231230h0vi6srww |
|
||||
0.21.0,231229ezidnxiu3 |
|
||||
0.20.2,231219aksf83aad |
|
||||
0.20.1,231218ywfaxax09 |
|
||||
0.20.0,231216nsyfew5j1 |
|
||||
0.19.1,2312156z2ric57n |
|
||||
0.19.0,231214per9qal2p |
|
||||
0.18.8,2312098ffjr3ign |
|
||||
0.18.7,23120880aolip2i |
|
||||
0.18.6,231207ueqazwde8 |
|
||||
0.18.5,231206jzy2n2sbi |
|
||||
0.18.4,2312033zjv5fqai |
|
||||
0.18.3,231203k2vnkxq2m |
|
||||
0.18.1,23120176kaer07t |
|
||||
0.17.0,231127p7iyxn8rg |
|
||||
0.16.0,231116rek2xuq6a |
|
||||
0.15.5,231115a5mv63u9f |
|
||||
0.15.4,23111469e1i3xyi |
|
||||
0.15.3,231113b0yv3uqem |
|
||||
0.15.2,231113ah0kuf3pf |
|
||||
0.15.1,231111yanyyovap |
|
||||
0.15.0,231110mdkomczmw |
|
||||
0.14.1,231109xitrgihlk |
|
||||
0.14.0,231102m6tuamwbx |
|
||||
0.13.4,231029rso7pso8l |
|
||||
0.13.3,231025uihnjkh9v |
|
||||
0.13.2,231024w4iv7xlm6 |
|
||||
0.13.1,231022f3j0ubckv |
|
||||
0.13.0,231022ptw6i4j42 |
|
||||
0.12.3,231008c5ursm0oj""" |
|
||||
|
|
||||
versions = parse_versions(data) |
|
||||
|
|
||||
# 生成 Markdown 文件 |
|
||||
markdown_content = generate_markdown(versions) |
|
||||
with open('Cursor历史.md', 'w', encoding='utf-8') as f: |
|
||||
f.write(markdown_content) |
|
||||
|
|
||||
# 创建结果数据结构 |
|
||||
result = { |
|
||||
"versions": [] |
|
||||
} |
|
||||
|
|
||||
# 处理每个版本 |
|
||||
for version in versions: |
|
||||
version_info = { |
|
||||
"version": version.version, |
|
||||
"build_id": version.build_id, |
|
||||
"downloads": version.get_download_links() |
|
||||
} |
|
||||
result["versions"].append(version_info) |
|
||||
|
|
||||
# 保存为JSON文件 |
|
||||
with open('cursor_downloads.json', 'w', encoding='utf-8') as f: |
|
||||
json.dump(result, f, indent=2, ensure_ascii=False) |
|
||||
|
|
||||
# 同时生成CSV格式的下载链接 |
|
||||
with open('cursor_downloads.csv', 'w', newline='', encoding='utf-8') as f: |
|
||||
writer = csv.writer(f) |
|
||||
writer.writerow(['Version', 'Platform', 'Architecture', 'Download URL']) |
|
||||
|
|
||||
for version in versions: |
|
||||
links = version.get_download_links() |
|
||||
for platform, archs in links.items(): |
|
||||
for arch, url in archs.items(): |
|
||||
writer.writerow([version.version, platform, arch, url]) |
|
||||
|
|
||||
if __name__ == "__main__": |
|
||||
main() |
|
@ -1,74 +0,0 @@ |
|||||
@echo off |
|
||||
setlocal EnableDelayedExpansion |
|
||||
|
|
||||
:: Build optimization flags |
|
||||
set "OPTIMIZATION_FLAGS=-trimpath -ldflags=\"-s -w\"" |
|
||||
set "BUILD_JOBS=4" |
|
||||
|
|
||||
:: Messages / 消息 |
|
||||
set "EN_MESSAGES[0]=Starting build process for version" |
|
||||
set "EN_MESSAGES[1]=Using optimization flags:" |
|
||||
set "EN_MESSAGES[2]=Cleaning old builds..." |
|
||||
set "EN_MESSAGES[3]=Cleanup completed" |
|
||||
set "EN_MESSAGES[4]=Starting builds for all platforms..." |
|
||||
set "EN_MESSAGES[5]=Building for" |
|
||||
set "EN_MESSAGES[6]=Build successful:" |
|
||||
set "EN_MESSAGES[7]=All builds completed!" |
|
||||
|
|
||||
:: Colors |
|
||||
set "GREEN=[32m" |
|
||||
set "RED=[31m" |
|
||||
set "RESET=[0m" |
|
||||
|
|
||||
:: Cleanup function |
|
||||
:cleanup |
|
||||
if exist "..\bin" ( |
|
||||
rd /s /q "..\bin" |
|
||||
echo %GREEN%!EN_MESSAGES[3]!%RESET% |
|
||||
) |
|
||||
mkdir "..\bin" 2>nul |
|
||||
|
|
||||
:: Build function with optimizations |
|
||||
:build |
|
||||
set "os=%~1" |
|
||||
set "arch=%~2" |
|
||||
set "ext=" |
|
||||
if "%os%"=="windows" set "ext=.exe" |
|
||||
|
|
||||
echo %GREEN%!EN_MESSAGES[5]! %os%/%arch%%RESET% |
|
||||
|
|
||||
set "CGO_ENABLED=0" |
|
||||
set "GOOS=%os%" |
|
||||
set "GOARCH=%arch%" |
|
||||
|
|
||||
start /b cmd /c "go build -trimpath -ldflags=\"-s -w\" -o ..\bin\%os%\%arch%\cursor-id-modifier%ext% -a -installsuffix cgo -mod=readonly ..\cmd\cursor-id-modifier" |
|
||||
exit /b 0 |
|
||||
|
|
||||
:: Main execution |
|
||||
echo %GREEN%!EN_MESSAGES[0]!%RESET% |
|
||||
echo %GREEN%!EN_MESSAGES[1]! %OPTIMIZATION_FLAGS%%RESET% |
|
||||
|
|
||||
call :cleanup |
|
||||
|
|
||||
echo %GREEN%!EN_MESSAGES[4]!%RESET% |
|
||||
|
|
||||
:: Start builds in parallel |
|
||||
set "pending=0" |
|
||||
for %%o in (windows linux darwin) do ( |
|
||||
for %%a in (amd64 386) do ( |
|
||||
call :build %%o %%a |
|
||||
set /a "pending+=1" |
|
||||
if !pending! geq %BUILD_JOBS% ( |
|
||||
timeout /t 1 /nobreak >nul |
|
||||
set "pending=0" |
|
||||
) |
|
||||
) |
|
||||
) |
|
||||
|
|
||||
:: Wait for all builds to complete |
|
||||
:wait_builds |
|
||||
timeout /t 2 /nobreak >nul |
|
||||
tasklist /fi "IMAGENAME eq go.exe" 2>nul | find "go.exe" >nul |
|
||||
if not errorlevel 1 goto wait_builds |
|
||||
|
|
||||
echo %GREEN%!EN_MESSAGES[7]!%RESET% |
|
@ -1,143 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# 设置颜色代码 / Set color codes |
|
||||
GREEN='\033[0;32m' |
|
||||
RED='\033[0;31m' |
|
||||
NC='\033[0m' # No Color / 无颜色 |
|
||||
|
|
||||
# Build optimization flags |
|
||||
OPTIMIZATION_FLAGS="-trimpath -ldflags=\"-s -w\"" |
|
||||
PARALLEL_JOBS=$(nproc || echo "4") # Get number of CPU cores or default to 4 |
|
||||
|
|
||||
# Messages / 消息 |
|
||||
EN_MESSAGES=( |
|
||||
"Starting build process for version" |
|
||||
"Cleaning old builds..." |
|
||||
"Creating bin directory..." |
|
||||
"Failed to create bin directory" |
|
||||
"Building for" |
|
||||
"Successfully built:" |
|
||||
"Failed to build for" |
|
||||
"Build Summary:" |
|
||||
"Successful builds:" |
|
||||
"Failed builds:" |
|
||||
"Generated files:" |
|
||||
) |
|
||||
|
|
||||
CN_MESSAGES=( |
|
||||
"开始构建版本" |
|
||||
"正在清理旧的构建文件..." |
|
||||
"正在创建bin目录..." |
|
||||
"创建bin目录失败" |
|
||||
"正在构建" |
|
||||
"构建成功:" |
|
||||
"构建失败:" |
|
||||
"构建摘要:" |
|
||||
"成功构建数:" |
|
||||
"失败构建数:" |
|
||||
"生成的文件:" |
|
||||
"构建过程被中断" |
|
||||
"错误:" |
|
||||
) |
|
||||
|
|
||||
# 版本信息 / Version info |
|
||||
VERSION="1.0.0" |
|
||||
|
|
||||
# Detect system language / 检测系统语言 |
|
||||
detect_language() { |
|
||||
if [[ $(locale | grep "LANG=zh_CN") ]]; then |
|
||||
echo "cn" |
|
||||
else |
|
||||
echo "en" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Get message based on language / 根据语言获取消息 |
|
||||
get_message() { |
|
||||
local index=$1 |
|
||||
local lang=$(detect_language) |
|
||||
|
|
||||
if [[ "$lang" == "cn" ]]; then |
|
||||
echo "${CN_MESSAGES[$index]}" |
|
||||
else |
|
||||
echo "${EN_MESSAGES[$index]}" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# 错误处理函数 / Error handling function |
|
||||
handle_error() { |
|
||||
echo -e "${RED}$(get_message 12) $1${NC}" |
|
||||
exit 1 |
|
||||
} |
|
||||
|
|
||||
# 清理函数 / Cleanup function |
|
||||
cleanup() { |
|
||||
if [ -d "../bin" ]; then |
|
||||
rm -rf ../bin |
|
||||
echo -e "${GREEN}$(get_message 1)${NC}" |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Build function with optimizations |
|
||||
build() { |
|
||||
local os=$1 |
|
||||
local arch=$2 |
|
||||
local ext="" |
|
||||
[ "$os" = "windows" ] && ext=".exe" |
|
||||
|
|
||||
echo -e "${GREEN}$(get_message 4) $os/$arch${NC}" |
|
||||
|
|
||||
GOOS=$os GOARCH=$arch CGO_ENABLED=0 go build \ |
|
||||
-trimpath \ |
|
||||
-ldflags="-s -w" \ |
|
||||
-o "../bin/$os/$arch/cursor-id-modifier$ext" \ |
|
||||
-a -installsuffix cgo \ |
|
||||
-mod=readonly \ |
|
||||
../cmd/cursor-id-modifier & |
|
||||
} |
|
||||
|
|
||||
# Parallel build execution |
|
||||
build_all() { |
|
||||
local builds=0 |
|
||||
local max_parallel=$PARALLEL_JOBS |
|
||||
|
|
||||
# Define build targets |
|
||||
declare -A targets=( |
|
||||
["linux/amd64"]=1 |
|
||||
["linux/386"]=1 |
|
||||
["linux/arm64"]=1 |
|
||||
["windows/amd64"]=1 |
|
||||
["windows/386"]=1 |
|
||||
["darwin/amd64"]=1 |
|
||||
["darwin/arm64"]=1 |
|
||||
) |
|
||||
|
|
||||
for target in "${!targets[@]}"; do |
|
||||
IFS='/' read -r os arch <<< "$target" |
|
||||
build "$os" "$arch" |
|
||||
|
|
||||
((builds++)) |
|
||||
|
|
||||
if ((builds >= max_parallel)); then |
|
||||
wait |
|
||||
builds=0 |
|
||||
fi |
|
||||
done |
|
||||
|
|
||||
# Wait for remaining builds |
|
||||
wait |
|
||||
} |
|
||||
|
|
||||
# Main execution |
|
||||
main() { |
|
||||
cleanup |
|
||||
mkdir -p ../bin || { echo -e "${RED}$(get_message 3)${NC}"; exit 1; } |
|
||||
build_all |
|
||||
echo -e "${GREEN}Build completed successfully${NC}" |
|
||||
} |
|
||||
|
|
||||
# 捕获错误信号 / Catch error signals |
|
||||
trap 'echo -e "\n${RED}$(get_message 11)${NC}"; exit 1' INT TERM |
|
||||
|
|
||||
# 执行主函数 / Execute main function |
|
||||
main |
|
@ -1,318 +0,0 @@ |
|||||
msgid "" |
|
||||
msgstr "" |
|
||||
"Project-Id-Version: cursor_id_modifier\n" |
|
||||
"POT-Creation-Date: 2025-04-25 12:00+0000\n" |
|
||||
"PO-Revision-Date: 2025-04-25 12:00+0000\n" |
|
||||
"Language-Team: None\n" |
|
||||
"MIME-Version: 1.0\n" |
|
||||
"Content-Type: text/plain; charset=UTF-8\n" |
|
||||
"Content-Transfer-Encoding: 8bit\n" |
|
||||
|
|
||||
msgid "Error: No translation file found for domain 'cursor_id_modifier' in {}/zh_CN/LC_MESSAGES/" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "========== Cursor ID modification tool log start {} ==========" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "[INFO] {} {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "[WARN] {} {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "[ERROR] {} {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "[DEBUG] {} {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "[CMD] {} Executing command: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "[CMD] {}:" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Unable to get username" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Finding Cursor installation path..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found Cursor installation path: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found Cursor via which: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Cursor executable not found, will try using config directory" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found Cursor via search: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Finding Cursor resource directory..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found Cursor resource directory: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found resource directory via binary path: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Cursor resource directory not found" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Please run this script with sudo" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Example: sudo {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Checking Cursor processes..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Getting process details for {}:" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "No running Cursor processes found" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found running Cursor processes" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Attempting to terminate Cursor processes..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Attempting to forcefully terminate processes..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Waiting for processes to terminate, attempt {}/{}..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Cursor processes successfully terminated" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Unable to terminate Cursor processes after {} attempts" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Please manually terminate the processes and try again" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Configuration file does not exist, skipping backup" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Configuration backed up to: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Backup failed" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "File does not exist: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Unable to modify file permissions: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Generated temporary file is empty" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Unable to write to file: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Machine code reset options" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Do you need to reset the machine code? (Usually, modifying JS files is sufficient):" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Don't reset - only modify JS files" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Reset - modify both config file and machine code" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "[INPUT_DEBUG] Machine code reset option selected: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "You chose to reset the machine code" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found existing configuration file: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Setting new device and machine IDs..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "New device ID: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "New machine ID: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Configuration file modified successfully" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Configuration file modification failed" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Configuration file not found, this is normal, skipping ID modification" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "You chose not to reset the machine code, will only modify JS files" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Configuration processing completed" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Finding Cursor's JS files..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Searching for JS files in resource directory: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found JS file: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "No JS files found in resource directory, trying other directories..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Searching directory: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "No modifiable JS files found" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found {} JS files to modify" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Starting to modify Cursor's JS files..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Unable to find modifiable JS files" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Processing file: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Unable to create backup for file: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found x-cursor-checksum setting code" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Successfully modified x-cursor-checksum setting code" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Failed to modify x-cursor-checksum setting code" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found IOPlatformUUID keyword" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Successfully injected randomUUID call into a$ function" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Failed to modify a$ function" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Successfully injected randomUUID call into v5 function" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Failed to modify v5 function" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Completed universal modification" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "File already contains custom injection code, skipping modification" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Completed most universal injection" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "File has already been modified, skipping modification" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Failed to modify any JS files" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Successfully modified {} JS files" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Disabling Cursor auto-update..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found update configuration file: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Disabled update configuration file: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Found updater: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Disabled updater: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "No update configuration files or updaters found" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Successfully disabled auto-update" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "You selected: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "This script only supports Linux systems" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Script started..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "System information: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Current user: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "System version information" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Cursor Linux startup tool" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Important notice" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "This tool prioritizes modifying JS files, which is safer and more reliable" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Modifying Cursor JS files..." |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "JS files modified successfully!" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "JS file modification failed, but configuration file modification may have succeeded" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "If Cursor still indicates the device is disabled after restarting, please rerun this script" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Please restart Cursor to apply the new configuration" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Follow the WeChat public account [Pancake AI] to discuss more Cursor tips and AI knowledge (script is free, join the group via the public account for more tips and experts)" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Script execution completed" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "========== Cursor ID modification tool log end {} ==========" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "Detailed log saved to: {}" |
|
||||
msgstr "" |
|
||||
|
|
||||
msgid "If you encounter issues, please provide this log file to the developer for troubleshooting" |
|
||||
msgstr "" |
|
1298
scripts/cursor_id_modifier.py
File diff suppressed because it is too large
View File
@ -1,9 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
REPO_DIR="$PWD" |
|
||||
LOCALES_DIR="$REPO_DIR/locales" |
|
||||
msginit -i cursor_id_modifier.pot -o $LOCALES_DIR/en_US/LC_MESSAGES/cursor_id_modifier.po -l en_US |
|
||||
for lang in en_US zh_CN; do |
|
||||
cd $LOCALES_DIR/$lang/LC_MESSAGES |
|
||||
msgfmt -o cursor_id_modifier.mo cursor_id_modifier.po |
|
||||
done |
|
||||
|
|
@ -1,193 +0,0 @@ |
|||||
# Check for admin rights and handle elevation |
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator") |
|
||||
if (-NOT $isAdmin) { |
|
||||
# Detect PowerShell version and path |
|
||||
$pwshPath = if (Get-Command "pwsh" -ErrorAction SilentlyContinue) { |
|
||||
(Get-Command "pwsh").Source # PowerShell 7+ |
|
||||
} elseif (Test-Path "$env:ProgramFiles\PowerShell\7\pwsh.exe") { |
|
||||
"$env:ProgramFiles\PowerShell\7\pwsh.exe" |
|
||||
} else { |
|
||||
"powershell.exe" # Windows PowerShell |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
Write-Host "`nRequesting administrator privileges..." -ForegroundColor Cyan |
|
||||
$scriptPath = $MyInvocation.MyCommand.Path |
|
||||
$argList = "-NoProfile -ExecutionPolicy Bypass -File `"$scriptPath`"" |
|
||||
Start-Process -FilePath $pwshPath -Verb RunAs -ArgumentList $argList -Wait |
|
||||
exit |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "`nError: Administrator privileges required" -ForegroundColor Red |
|
||||
Write-Host "Please run this script from an Administrator PowerShell window" -ForegroundColor Yellow |
|
||||
Write-Host "`nTo do this:" -ForegroundColor Cyan |
|
||||
Write-Host "1. Press Win + X" -ForegroundColor White |
|
||||
Write-Host "2. Click 'Windows Terminal (Admin)' or 'PowerShell (Admin)'" -ForegroundColor White |
|
||||
Write-Host "3. Run the installation command again" -ForegroundColor White |
|
||||
Write-Host "`nPress enter to exit..." |
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') |
|
||||
exit 1 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# Set TLS to 1.2 |
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 |
|
||||
|
|
||||
# Create temporary directory |
|
||||
$TmpDir = Join-Path $env:TEMP ([System.Guid]::NewGuid().ToString()) |
|
||||
New-Item -ItemType Directory -Path $TmpDir | Out-Null |
|
||||
|
|
||||
# Cleanup function |
|
||||
function Cleanup { |
|
||||
if (Test-Path $TmpDir) { |
|
||||
Remove-Item -Recurse -Force $TmpDir |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# Error handler |
|
||||
trap { |
|
||||
Write-Host "Error: $_" -ForegroundColor Red |
|
||||
Cleanup |
|
||||
Write-Host "Press enter to exit..." |
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') |
|
||||
exit 1 |
|
||||
} |
|
||||
|
|
||||
# Detect system architecture |
|
||||
function Get-SystemArch { |
|
||||
if ([Environment]::Is64BitOperatingSystem) { |
|
||||
return "x86_64" |
|
||||
} else { |
|
||||
return "i386" |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# Download with progress |
|
||||
function Get-FileWithProgress { |
|
||||
param ( |
|
||||
[string]$Url, |
|
||||
[string]$OutputFile |
|
||||
) |
|
||||
|
|
||||
try { |
|
||||
$webClient = New-Object System.Net.WebClient |
|
||||
$webClient.Headers.Add("User-Agent", "PowerShell Script") |
|
||||
|
|
||||
$webClient.DownloadFile($Url, $OutputFile) |
|
||||
return $true |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "Failed to download: $_" -ForegroundColor Red |
|
||||
return $false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# Main installation function |
|
||||
function Install-CursorModifier { |
|
||||
Write-Host "Starting installation..." -ForegroundColor Cyan |
|
||||
|
|
||||
# Detect architecture |
|
||||
$arch = Get-SystemArch |
|
||||
Write-Host "Detected architecture: $arch" -ForegroundColor Green |
|
||||
|
|
||||
# Set installation directory |
|
||||
$InstallDir = "$env:ProgramFiles\CursorModifier" |
|
||||
if (!(Test-Path $InstallDir)) { |
|
||||
New-Item -ItemType Directory -Path $InstallDir | Out-Null |
|
||||
} |
|
||||
|
|
||||
# Get latest release |
|
||||
try { |
|
||||
$latestRelease = Invoke-RestMethod -Uri "https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" |
|
||||
Write-Host "Found latest release: $($latestRelease.tag_name)" -ForegroundColor Cyan |
|
||||
|
|
||||
# Look for Windows binary with our architecture |
|
||||
$version = $latestRelease.tag_name.TrimStart('v') |
|
||||
Write-Host "Version: $version" -ForegroundColor Cyan |
|
||||
$possibleNames = @( |
|
||||
"cursor-id-modifier_${version}_windows_x86_64.exe", |
|
||||
"cursor-id-modifier_${version}_windows_$($arch).exe" |
|
||||
) |
|
||||
|
|
||||
$asset = $null |
|
||||
foreach ($name in $possibleNames) { |
|
||||
Write-Host "Checking for asset: $name" -ForegroundColor Cyan |
|
||||
$asset = $latestRelease.assets | Where-Object { $_.name -eq $name } |
|
||||
if ($asset) { |
|
||||
Write-Host "Found matching asset: $($asset.name)" -ForegroundColor Green |
|
||||
break |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
if (!$asset) { |
|
||||
Write-Host "`nAvailable assets:" -ForegroundColor Yellow |
|
||||
$latestRelease.assets | ForEach-Object { Write-Host "- $($_.name)" } |
|
||||
throw "Could not find appropriate Windows binary for $arch architecture" |
|
||||
} |
|
||||
|
|
||||
$downloadUrl = $asset.browser_download_url |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "Failed to get latest release: $_" -ForegroundColor Red |
|
||||
exit 1 |
|
||||
} |
|
||||
|
|
||||
# Download binary |
|
||||
Write-Host "`nDownloading latest release..." -ForegroundColor Cyan |
|
||||
$binaryPath = Join-Path $TmpDir "cursor-id-modifier.exe" |
|
||||
|
|
||||
if (!(Get-FileWithProgress -Url $downloadUrl -OutputFile $binaryPath)) { |
|
||||
exit 1 |
|
||||
} |
|
||||
|
|
||||
# Install binary |
|
||||
Write-Host "Installing..." -ForegroundColor Cyan |
|
||||
try { |
|
||||
Copy-Item -Path $binaryPath -Destination "$InstallDir\cursor-id-modifier.exe" -Force |
|
||||
|
|
||||
# Add to PATH if not already present |
|
||||
$currentPath = [Environment]::GetEnvironmentVariable("Path", "Machine") |
|
||||
if ($currentPath -notlike "*$InstallDir*") { |
|
||||
[Environment]::SetEnvironmentVariable("Path", "$currentPath;$InstallDir", "Machine") |
|
||||
} |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "Failed to install: $_" -ForegroundColor Red |
|
||||
exit 1 |
|
||||
} |
|
||||
|
|
||||
Write-Host "Installation completed successfully!" -ForegroundColor Green |
|
||||
Write-Host "Running cursor-id-modifier..." -ForegroundColor Cyan |
|
||||
|
|
||||
# Run the program |
|
||||
try { |
|
||||
& "$InstallDir\cursor-id-modifier.exe" |
|
||||
if ($LASTEXITCODE -ne 0) { |
|
||||
Write-Host "Failed to run cursor-id-modifier" -ForegroundColor Red |
|
||||
exit 1 |
|
||||
} |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "Failed to run cursor-id-modifier: $_" -ForegroundColor Red |
|
||||
exit 1 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# Run installation |
|
||||
try { |
|
||||
Install-CursorModifier |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "Installation failed: $_" -ForegroundColor Red |
|
||||
Cleanup |
|
||||
Write-Host "Press enter to exit..." |
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') |
|
||||
exit 1 |
|
||||
} |
|
||||
finally { |
|
||||
Cleanup |
|
||||
if ($LASTEXITCODE -ne 0) { |
|
||||
Write-Host "Press enter to exit..." -ForegroundColor Green |
|
||||
$null = $Host.UI.RawUI.ReadKey('NoEcho,IncludeKeyDown') |
|
||||
} |
|
||||
} |
|
@ -1,127 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
set -e |
|
||||
|
|
||||
# Colors for output |
|
||||
RED='\033[0;31m' |
|
||||
GREEN='\033[0;32m' |
|
||||
BLUE='\033[0;36m' |
|
||||
YELLOW='\033[0;33m' |
|
||||
NC='\033[0m' |
|
||||
|
|
||||
# Temporary directory for downloads |
|
||||
TMP_DIR=$(mktemp -d) |
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT |
|
||||
|
|
||||
# Check for required commands |
|
||||
check_requirements() { |
|
||||
if ! command -v curl >/dev/null 2>&1; then |
|
||||
echo -e "${RED}Error: curl is required${NC}" |
|
||||
exit 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Detect system information |
|
||||
detect_system() { |
|
||||
local os arch suffix |
|
||||
|
|
||||
case "$(uname -s)" in |
|
||||
Linux*) os="linux";; |
|
||||
Darwin*) os="darwin";; |
|
||||
*) echo -e "${RED}Unsupported OS${NC}"; exit 1;; |
|
||||
esac |
|
||||
|
|
||||
case "$(uname -m)" in |
|
||||
x86_64) |
|
||||
arch="x86_64" |
|
||||
;; |
|
||||
aarch64|arm64) |
|
||||
arch="arm64" |
|
||||
;; |
|
||||
i386|i686) |
|
||||
arch="i386" |
|
||||
;; |
|
||||
*) echo -e "${RED}Unsupported architecture${NC}"; exit 1;; |
|
||||
esac |
|
||||
|
|
||||
echo "$os $arch" |
|
||||
} |
|
||||
|
|
||||
# Download with progress |
|
||||
download() { |
|
||||
local url="$1" |
|
||||
local output="$2" |
|
||||
curl -#L "$url" -o "$output" |
|
||||
} |
|
||||
|
|
||||
# Check and create installation directory |
|
||||
setup_install_dir() { |
|
||||
local install_dir="$1" |
|
||||
|
|
||||
if [ ! -d "$install_dir" ]; then |
|
||||
mkdir -p "$install_dir" || { |
|
||||
echo -e "${RED}Failed to create installation directory${NC}" |
|
||||
exit 1 |
|
||||
} |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# Main installation function |
|
||||
main() { |
|
||||
check_requirements |
|
||||
|
|
||||
echo -e "${BLUE}Starting installation...${NC}" |
|
||||
|
|
||||
# Detect system |
|
||||
read -r OS ARCH SUFFIX <<< "$(detect_system)" |
|
||||
echo -e "${GREEN}Detected: $OS $ARCH${NC}" |
|
||||
|
|
||||
# Set installation directory |
|
||||
INSTALL_DIR="/usr/local/bin" |
|
||||
|
|
||||
# Setup installation directory |
|
||||
setup_install_dir "$INSTALL_DIR" |
|
||||
|
|
||||
# Get latest release info |
|
||||
echo -e "${BLUE}Fetching latest release information...${NC}" |
|
||||
LATEST_URL="https://api.github.com/repos/yuaotian/go-cursor-help/releases/latest" |
|
||||
|
|
||||
# Get latest version and remove 'v' prefix |
|
||||
VERSION=$(curl -s "$LATEST_URL" | grep "tag_name" | cut -d'"' -f4 | sed 's/^v//') |
|
||||
|
|
||||
# Construct binary name |
|
||||
BINARY_NAME="cursor-id-modifier_${VERSION}_${OS}_${ARCH}" |
|
||||
echo -e "${BLUE}Looking for asset: $BINARY_NAME${NC}" |
|
||||
|
|
||||
# Get download URL directly |
|
||||
DOWNLOAD_URL=$(curl -s "$LATEST_URL" | grep -o "\"browser_download_url\": \"[^\"]*${BINARY_NAME}[^\"]*\"" | cut -d'"' -f4) |
|
||||
|
|
||||
if [ -z "$DOWNLOAD_URL" ]; then |
|
||||
echo -e "${RED}Error: Could not find appropriate binary for $OS $ARCH${NC}" |
|
||||
echo -e "${YELLOW}Available assets:${NC}" |
|
||||
curl -s "$LATEST_URL" | grep "browser_download_url" | cut -d'"' -f4 |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
echo -e "${GREEN}Found matching asset: $BINARY_NAME${NC}" |
|
||||
echo -e "${BLUE}Downloading from: $DOWNLOAD_URL${NC}" |
|
||||
|
|
||||
download "$DOWNLOAD_URL" "$TMP_DIR/cursor-id-modifier" |
|
||||
|
|
||||
# Install binary |
|
||||
echo -e "${BLUE}Installing...${NC}" |
|
||||
chmod +x "$TMP_DIR/cursor-id-modifier" |
|
||||
sudo mv "$TMP_DIR/cursor-id-modifier" "$INSTALL_DIR/" |
|
||||
|
|
||||
echo -e "${GREEN}Installation completed successfully!${NC}" |
|
||||
echo -e "${BLUE}Running cursor-id-modifier...${NC}" |
|
||||
|
|
||||
# Run the program with sudo, preserving environment variables |
|
||||
export AUTOMATED_MODE=1 |
|
||||
if ! sudo -E cursor-id-modifier; then |
|
||||
echo -e "${RED}Failed to run cursor-id-modifier${NC}" |
|
||||
exit 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
main |
|
1293
scripts/run/cursor_linux_id_modifier.sh
File diff suppressed because it is too large
View File
1401
scripts/run/cursor_mac_free_trial_reset.sh
File diff suppressed because it is too large
View File
3210
scripts/run/cursor_mac_id_modifier.sh
File diff suppressed because it is too large
View File
@ -1,688 +0,0 @@ |
|||||
#!/bin/bash |
|
||||
|
|
||||
# ======================================== |
|
||||
# Cursor macOS 机器码修改脚本 (重构精简版) |
|
||||
# ======================================== |
|
||||
# |
|
||||
# 🎯 重构目标: |
|
||||
# - 简化脚本复杂度,从3158行压缩到约900行 |
|
||||
# - 自动化权限修复,解决EACCES权限错误 |
|
||||
# - 减少用户交互步骤,提升执行效率 |
|
||||
# - 保持所有原有功能完整性 |
|
||||
# |
|
||||
# 🚀 执行流程说明: |
|
||||
# 1. 环境检测和权限预修复 |
|
||||
# 2. 用户选择执行模式(仅修改 vs 完整重置) |
|
||||
# 3. 自动执行所有必要步骤 |
|
||||
# 4. 智能设备识别绕过(MAC地址或JS内核修改) |
|
||||
# 5. 自动权限修复和验证 |
|
||||
# |
|
||||
# ======================================== |
|
||||
|
|
||||
set -e |
|
||||
|
|
||||
# ==================== 核心配置 ==================== |
|
||||
LOG_FILE="/tmp/cursor_reset_$(date +%Y%m%d_%H%M%S).log" |
|
||||
CURSOR_APP_PATH="/Applications/Cursor.app" |
|
||||
STORAGE_FILE="$HOME/Library/Application Support/Cursor/User/globalStorage/storage.json" |
|
||||
BACKUP_DIR="$HOME/Library/Application Support/Cursor/User/globalStorage/backups" |
|
||||
|
|
||||
# 颜色定义 |
|
||||
RED='\033[0;31m' |
|
||||
GREEN='\033[0;32m' |
|
||||
YELLOW='\033[1;33m' |
|
||||
BLUE='\033[0;34m' |
|
||||
NC='\033[0m' |
|
||||
|
|
||||
# ==================== 统一工具函数 ==================== |
|
||||
|
|
||||
# 初始化日志 |
|
||||
init_log() { |
|
||||
echo "========== Cursor重构脚本执行日志 $(date) ==========" > "$LOG_FILE" |
|
||||
chmod 644 "$LOG_FILE" |
|
||||
} |
|
||||
|
|
||||
# 精简日志函数 |
|
||||
log() { |
|
||||
local level="$1" |
|
||||
local msg="$2" |
|
||||
local color="" |
|
||||
|
|
||||
case "$level" in |
|
||||
"INFO") color="$GREEN" ;; |
|
||||
"WARN") color="$YELLOW" ;; |
|
||||
"ERROR") color="$RED" ;; |
|
||||
*) color="$BLUE" ;; |
|
||||
esac |
|
||||
|
|
||||
echo -e "${color}[$level]${NC} $msg" |
|
||||
echo "[$level] $(date '+%H:%M:%S') $msg" >> "$LOG_FILE" |
|
||||
} |
|
||||
|
|
||||
# 统一权限管理器 - 解决所有权限问题 |
|
||||
fix_permissions() { |
|
||||
log "INFO" "🔧 执行统一权限修复..." |
|
||||
|
|
||||
local cursor_support="$HOME/Library/Application Support/Cursor" |
|
||||
local cursor_home="$HOME/.cursor" |
|
||||
|
|
||||
# 创建必要目录结构 |
|
||||
local dirs=( |
|
||||
"$cursor_support" |
|
||||
"$cursor_support/User" |
|
||||
"$cursor_support/User/globalStorage" |
|
||||
"$cursor_support/logs" |
|
||||
"$cursor_support/CachedData" |
|
||||
"$cursor_home" |
|
||||
"$cursor_home/extensions" |
|
||||
) |
|
||||
|
|
||||
for dir in "${dirs[@]}"; do |
|
||||
mkdir -p "$dir" 2>/dev/null || true |
|
||||
done |
|
||||
|
|
||||
# 执行核心权限修复命令 |
|
||||
if sudo chown -R "$(whoami)" "$cursor_support" 2>/dev/null && \ |
|
||||
sudo chown -R "$(whoami)" "$cursor_home" 2>/dev/null && \ |
|
||||
chmod -R u+w "$cursor_support" 2>/dev/null && \ |
|
||||
chmod -R u+w "$cursor_home/extensions" 2>/dev/null; then |
|
||||
log "INFO" "✅ 权限修复成功" |
|
||||
return 0 |
|
||||
else |
|
||||
log "ERROR" "❌ 权限修复失败" |
|
||||
return 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# 环境检测器 |
|
||||
detect_environment() { |
|
||||
log "INFO" "🔍 检测系统环境..." |
|
||||
|
|
||||
# 检测macOS版本和硬件 |
|
||||
MACOS_VERSION=$(sw_vers -productVersion) |
|
||||
HARDWARE_TYPE=$(uname -m) |
|
||||
|
|
||||
if [[ "$HARDWARE_TYPE" == "arm64" ]]; then |
|
||||
HARDWARE_TYPE="Apple Silicon" |
|
||||
else |
|
||||
HARDWARE_TYPE="Intel" |
|
||||
fi |
|
||||
|
|
||||
# 检测兼容性 |
|
||||
local macos_major=$(echo "$MACOS_VERSION" | cut -d. -f1) |
|
||||
if [[ $macos_major -ge 14 ]] || [[ "$HARDWARE_TYPE" == "Apple Silicon" ]]; then |
|
||||
MAC_COMPATIBLE=false |
|
||||
log "WARN" "⚠️ 检测到MAC地址修改受限环境: macOS $MACOS_VERSION ($HARDWARE_TYPE)" |
|
||||
else |
|
||||
MAC_COMPATIBLE=true |
|
||||
log "INFO" "✅ 环境兼容性检查通过" |
|
||||
fi |
|
||||
|
|
||||
# 检查Python3 |
|
||||
if ! command -v python3 >/dev/null 2>&1; then |
|
||||
log "ERROR" "❌ 未找到Python3,请安装: brew install python3" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
# 检查Cursor应用 |
|
||||
if [ ! -d "$CURSOR_APP_PATH" ]; then |
|
||||
log "ERROR" "❌ 未找到Cursor应用: $CURSOR_APP_PATH" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
log "INFO" "✅ 环境检测完成: macOS $MACOS_VERSION ($HARDWARE_TYPE)" |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
# 进程管理器 |
|
||||
manage_cursor_process() { |
|
||||
local action="$1" # kill 或 start |
|
||||
|
|
||||
case "$action" in |
|
||||
"kill") |
|
||||
log "INFO" "🔄 关闭Cursor进程..." |
|
||||
pkill -f "Cursor" 2>/dev/null || true |
|
||||
sleep 2 |
|
||||
|
|
||||
# 验证是否关闭 |
|
||||
if pgrep -f "Cursor" >/dev/null; then |
|
||||
pkill -9 -f "Cursor" 2>/dev/null || true |
|
||||
sleep 2 |
|
||||
fi |
|
||||
log "INFO" "✅ Cursor进程已关闭" |
|
||||
;; |
|
||||
"start") |
|
||||
log "INFO" "🚀 启动Cursor..." |
|
||||
"$CURSOR_APP_PATH/Contents/MacOS/Cursor" > /dev/null 2>&1 & |
|
||||
sleep 15 |
|
||||
log "INFO" "✅ Cursor已启动" |
|
||||
;; |
|
||||
esac |
|
||||
} |
|
||||
|
|
||||
# 错误处理器 |
|
||||
handle_error() { |
|
||||
local error_msg="$1" |
|
||||
local recovery_action="$2" |
|
||||
|
|
||||
log "ERROR" "❌ 错误: $error_msg" |
|
||||
|
|
||||
if [ -n "$recovery_action" ]; then |
|
||||
log "INFO" "🔄 尝试恢复: $recovery_action" |
|
||||
eval "$recovery_action" |
|
||||
fi |
|
||||
|
|
||||
log "INFO" "💡 如需帮助,请查看日志: $LOG_FILE" |
|
||||
} |
|
||||
|
|
||||
# ==================== 功能模块 ==================== |
|
||||
|
|
||||
# 机器码修改器 |
|
||||
modify_machine_code() { |
|
||||
log "INFO" "🛠️ 开始修改机器码配置..." |
|
||||
|
|
||||
# 检查配置文件 |
|
||||
if [ ! -f "$STORAGE_FILE" ]; then |
|
||||
log "ERROR" "❌ 配置文件不存在,请先启动Cursor生成配置" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
# 创建备份 |
|
||||
mkdir -p "$BACKUP_DIR" |
|
||||
local backup_file="$BACKUP_DIR/storage.json.backup_$(date +%Y%m%d_%H%M%S)" |
|
||||
cp "$STORAGE_FILE" "$backup_file" || { |
|
||||
log "ERROR" "❌ 备份失败" |
|
||||
return 1 |
|
||||
} |
|
||||
|
|
||||
# 生成新ID |
|
||||
local machine_id="auth0|user_$(openssl rand -hex 16)" |
|
||||
local mac_machine_id=$(uuidgen | tr '[:upper:]' '[:lower:]') |
|
||||
local device_id=$(uuidgen | tr '[:upper:]' '[:lower:]') |
|
||||
local sqm_id="{$(uuidgen | tr '[:lower:]' '[:upper:]')}" |
|
||||
|
|
||||
# 修改配置文件 |
|
||||
local python_result=$(python3 -c " |
|
||||
import json |
|
||||
import sys |
|
||||
|
|
||||
try: |
|
||||
with open('$STORAGE_FILE', 'r', encoding='utf-8') as f: |
|
||||
config = json.load(f) |
|
||||
|
|
||||
config['telemetry.machineId'] = '$machine_id' |
|
||||
config['telemetry.macMachineId'] = '$mac_machine_id' |
|
||||
config['telemetry.devDeviceId'] = '$device_id' |
|
||||
config['telemetry.sqmId'] = '$sqm_id' |
|
||||
|
|
||||
with open('$STORAGE_FILE', 'w', encoding='utf-8') as f: |
|
||||
json.dump(config, f, indent=2, ensure_ascii=False) |
|
||||
|
|
||||
print('SUCCESS') |
|
||||
except Exception as e: |
|
||||
print(f'ERROR: {e}') |
|
||||
sys.exit(1) |
|
||||
" 2>&1) |
|
||||
|
|
||||
if echo "$python_result" | grep -q "SUCCESS"; then |
|
||||
# 设置只读保护 |
|
||||
chmod 444 "$STORAGE_FILE" 2>/dev/null || true |
|
||||
log "INFO" "✅ 机器码修改成功" |
|
||||
log "INFO" "💾 备份保存至: $(basename "$backup_file")" |
|
||||
return 0 |
|
||||
else |
|
||||
log "ERROR" "❌ 机器码修改失败: $python_result" |
|
||||
cp "$backup_file" "$STORAGE_FILE" 2>/dev/null || true |
|
||||
return 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# 智能设备绕过器 - 根据环境自动选择最佳方案 |
|
||||
bypass_device_detection() { |
|
||||
log "INFO" "🔧 开始智能设备识别绕过..." |
|
||||
|
|
||||
# 根据环境兼容性选择方案 |
|
||||
if [ "$MAC_COMPATIBLE" = false ]; then |
|
||||
log "INFO" "💡 检测到MAC地址修改受限,使用JS内核修改方案" |
|
||||
return modify_js_kernel |
|
||||
else |
|
||||
log "INFO" "💡 尝试MAC地址修改方案" |
|
||||
if modify_mac_address; then |
|
||||
return 0 |
|
||||
else |
|
||||
log "WARN" "⚠️ MAC地址修改失败,切换到JS内核修改" |
|
||||
return modify_js_kernel |
|
||||
fi |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# MAC地址修改器(简化版) |
|
||||
modify_mac_address() { |
|
||||
log "INFO" "🌐 开始MAC地址修改..." |
|
||||
|
|
||||
# 获取活动网络接口 |
|
||||
local interfaces=() |
|
||||
while IFS= read -r line; do |
|
||||
if [[ $line == "Hardware Port: Wi-Fi" || $line == "Hardware Port: Ethernet" ]]; then |
|
||||
read -r dev_line |
|
||||
local device=$(echo "$dev_line" | awk '{print $2}') |
|
||||
if [ -n "$device" ] && ifconfig "$device" 2>/dev/null | grep -q "status: active"; then |
|
||||
interfaces+=("$device") |
|
||||
fi |
|
||||
fi |
|
||||
done < <(networksetup -listallhardwareports) |
|
||||
|
|
||||
if [ ${#interfaces[@]} -eq 0 ]; then |
|
||||
log "WARN" "⚠️ 未找到活动网络接口" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
local success_count=0 |
|
||||
for interface in "${interfaces[@]}"; do |
|
||||
log "INFO" "🔧 处理接口: $interface" |
|
||||
|
|
||||
# 生成新MAC地址 |
|
||||
local new_mac=$(printf '%02x:%02x:%02x:%02x:%02x:%02x' \ |
|
||||
$(( (RANDOM & 0xFC) | 0x02 )) $((RANDOM%256)) $((RANDOM%256)) \ |
|
||||
$((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256))) |
|
||||
|
|
||||
# 尝试修改MAC地址 |
|
||||
if sudo ifconfig "$interface" down 2>/dev/null && \ |
|
||||
sleep 2 && \ |
|
||||
sudo ifconfig "$interface" ether "$new_mac" 2>/dev/null && \ |
|
||||
sudo ifconfig "$interface" up 2>/dev/null; then |
|
||||
log "INFO" "✅ 接口 $interface MAC地址修改成功: $new_mac" |
|
||||
((success_count++)) |
|
||||
else |
|
||||
log "WARN" "⚠️ 接口 $interface MAC地址修改失败" |
|
||||
fi |
|
||||
sleep 2 |
|
||||
done |
|
||||
|
|
||||
if [ $success_count -gt 0 ]; then |
|
||||
log "INFO" "✅ MAC地址修改完成 ($success_count/${#interfaces[@]} 成功)" |
|
||||
return 0 |
|
||||
else |
|
||||
return 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# JS内核修改器(简化版) |
|
||||
modify_js_kernel() { |
|
||||
log "INFO" "🔧 开始JS内核修改..." |
|
||||
|
|
||||
# 关闭Cursor |
|
||||
manage_cursor_process "kill" |
|
||||
|
|
||||
# 目标JS文件 |
|
||||
local js_files=( |
|
||||
"$CURSOR_APP_PATH/Contents/Resources/app/out/vs/workbench/api/node/extensionHostProcess.js" |
|
||||
"$CURSOR_APP_PATH/Contents/Resources/app/out/main.js" |
|
||||
) |
|
||||
|
|
||||
# 检查是否需要修改 |
|
||||
local need_modify=false |
|
||||
for file in "${js_files[@]}"; do |
|
||||
if [ -f "$file" ] && ! grep -q "return crypto.randomUUID()" "$file" 2>/dev/null; then |
|
||||
need_modify=true |
|
||||
break |
|
||||
fi |
|
||||
done |
|
||||
|
|
||||
if [ "$need_modify" = false ]; then |
|
||||
log "INFO" "✅ JS文件已修改,跳过" |
|
||||
return 0 |
|
||||
fi |
|
||||
|
|
||||
# 创建备份 |
|
||||
local backup_app="/tmp/Cursor.app.backup_$(date +%Y%m%d_%H%M%S)" |
|
||||
cp -R "$CURSOR_APP_PATH" "$backup_app" || { |
|
||||
log "ERROR" "❌ 创建备份失败" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
# 生成设备ID |
|
||||
local new_uuid=$(uuidgen | tr '[:upper:]' '[:lower:]') |
|
||||
local machine_id="auth0|user_$(openssl rand -hex 16)" |
|
||||
|
|
||||
# 修改JS文件 |
|
||||
local modified_count=0 |
|
||||
for file in "${js_files[@]}"; do |
|
||||
if [ ! -f "$file" ]; then |
|
||||
continue |
|
||||
fi |
|
||||
|
|
||||
log "INFO" "📝 处理文件: $(basename "$file")" |
|
||||
|
|
||||
# 创建注入代码 |
|
||||
local inject_code=" |
|
||||
// Cursor设备标识符劫持 - $(date +%Y%m%d%H%M%S) |
|
||||
import crypto from 'crypto'; |
|
||||
const originalRandomUUID = crypto.randomUUID; |
|
||||
crypto.randomUUID = function() { return '$new_uuid'; }; |
|
||||
globalThis.getMachineId = function() { return '$machine_id'; }; |
|
||||
console.log('Cursor设备标识符已劫持'); |
|
||||
" |
|
||||
|
|
||||
# 注入代码 |
|
||||
echo "$inject_code" > "${file}.new" |
|
||||
cat "$file" >> "${file}.new" |
|
||||
mv "${file}.new" "$file" |
|
||||
|
|
||||
((modified_count++)) |
|
||||
log "INFO" "✅ 文件修改成功: $(basename "$file")" |
|
||||
done |
|
||||
|
|
||||
if [ $modified_count -gt 0 ]; then |
|
||||
# 重新签名 |
|
||||
if codesign --sign - --force --deep "$CURSOR_APP_PATH" 2>/dev/null; then |
|
||||
log "INFO" "✅ JS内核修改完成 ($modified_count 个文件)" |
|
||||
return 0 |
|
||||
else |
|
||||
log "WARN" "⚠️ 签名失败,但修改已完成" |
|
||||
return 0 |
|
||||
fi |
|
||||
else |
|
||||
log "ERROR" "❌ 未修改任何文件" |
|
||||
return 1 |
|
||||
fi |
|
||||
} |
|
||||
|
|
||||
# 环境重置器 |
|
||||
reset_environment() { |
|
||||
log "INFO" "🗑️ 开始环境重置..." |
|
||||
|
|
||||
# 关闭Cursor |
|
||||
manage_cursor_process "kill" |
|
||||
|
|
||||
# 删除目标文件夹 |
|
||||
local folders=( |
|
||||
"$HOME/Library/Application Support/Cursor" |
|
||||
"$HOME/.cursor" |
|
||||
) |
|
||||
|
|
||||
local deleted_count=0 |
|
||||
for folder in "${folders[@]}"; do |
|
||||
if [ -d "$folder" ]; then |
|
||||
if rm -rf "$folder"; then |
|
||||
log "INFO" "✅ 已删除: $folder" |
|
||||
((deleted_count++)) |
|
||||
else |
|
||||
log "ERROR" "❌ 删除失败: $folder" |
|
||||
fi |
|
||||
fi |
|
||||
done |
|
||||
|
|
||||
# 修复权限 |
|
||||
fix_permissions |
|
||||
|
|
||||
log "INFO" "✅ 环境重置完成 (删除 $deleted_count 个文件夹)" |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
# 禁用自动更新 |
|
||||
disable_auto_update() { |
|
||||
log "INFO" "🚫 禁用自动更新..." |
|
||||
|
|
||||
local app_update_yml="$CURSOR_APP_PATH/Contents/Resources/app-update.yml" |
|
||||
local updater_path="$HOME/Library/Application Support/Caches/cursor-updater" |
|
||||
|
|
||||
# 禁用app-update.yml |
|
||||
if [ -f "$app_update_yml" ]; then |
|
||||
sudo cp "$app_update_yml" "${app_update_yml}.bak" 2>/dev/null || true |
|
||||
sudo bash -c "echo '' > \"$app_update_yml\"" 2>/dev/null || true |
|
||||
sudo chmod 444 "$app_update_yml" 2>/dev/null || true |
|
||||
fi |
|
||||
|
|
||||
# 禁用cursor-updater |
|
||||
sudo rm -rf "$updater_path" 2>/dev/null || true |
|
||||
sudo touch "$updater_path" 2>/dev/null || true |
|
||||
sudo chmod 444 "$updater_path" 2>/dev/null || true |
|
||||
|
|
||||
log "INFO" "✅ 自动更新已禁用" |
|
||||
} |
|
||||
|
|
||||
# 修复应用签名问题 |
|
||||
fix_app_signature() { |
|
||||
log "INFO" "🔧 修复应用签名..." |
|
||||
|
|
||||
# 移除隔离属性 |
|
||||
sudo find "$CURSOR_APP_PATH" -print0 2>/dev/null | \ |
|
||||
xargs -0 sudo xattr -d com.apple.quarantine 2>/dev/null || true |
|
||||
|
|
||||
# 重新签名 |
|
||||
sudo codesign --force --deep --sign - "$CURSOR_APP_PATH" 2>/dev/null || true |
|
||||
|
|
||||
log "INFO" "✅ 应用签名修复完成" |
|
||||
} |
|
||||
|
|
||||
# ==================== 主执行流程 ==================== |
|
||||
|
|
||||
# 快速模式 - 仅修改机器码 |
|
||||
quick_mode() { |
|
||||
log "INFO" "🚀 执行快速模式(仅修改机器码)..." |
|
||||
|
|
||||
# 检查环境 |
|
||||
if ! detect_environment; then |
|
||||
handle_error "环境检测失败" "exit 1" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
# 预修复权限 |
|
||||
fix_permissions |
|
||||
|
|
||||
# 修改机器码 |
|
||||
if ! modify_machine_code; then |
|
||||
handle_error "机器码修改失败" "exit 1" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
# 设备绕过 |
|
||||
bypass_device_detection || log "WARN" "⚠️ 设备绕过失败,但机器码修改已完成" |
|
||||
|
|
||||
# 禁用更新 |
|
||||
disable_auto_update |
|
||||
|
|
||||
# 修复签名 |
|
||||
fix_app_signature |
|
||||
|
|
||||
# 最终权限修复 |
|
||||
fix_permissions |
|
||||
|
|
||||
log "INFO" "🎉 快速模式执行完成!" |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
# 完整模式 - 重置环境+修改机器码 |
|
||||
full_mode() { |
|
||||
log "INFO" "🚀 执行完整模式(重置环境+修改机器码)..." |
|
||||
|
|
||||
# 检查环境 |
|
||||
if ! detect_environment; then |
|
||||
handle_error "环境检测失败" "exit 1" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
# 环境重置 |
|
||||
if ! reset_environment; then |
|
||||
handle_error "环境重置失败" "exit 1" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
# 启动Cursor生成配置 |
|
||||
manage_cursor_process "start" |
|
||||
|
|
||||
# 等待配置文件生成 |
|
||||
local config_wait=0 |
|
||||
while [ ! -f "$STORAGE_FILE" ] && [ $config_wait -lt 30 ]; do |
|
||||
sleep 2 |
|
||||
((config_wait += 2)) |
|
||||
log "INFO" "⏳ 等待配置文件生成... ($config_wait/30秒)" |
|
||||
done |
|
||||
|
|
||||
# 关闭Cursor |
|
||||
manage_cursor_process "kill" |
|
||||
|
|
||||
# 修改机器码 |
|
||||
if ! modify_machine_code; then |
|
||||
handle_error "机器码修改失败" "exit 1" |
|
||||
return 1 |
|
||||
fi |
|
||||
|
|
||||
# 设备绕过 |
|
||||
bypass_device_detection || log "WARN" "⚠️ 设备绕过失败,但机器码修改已完成" |
|
||||
|
|
||||
# 禁用更新 |
|
||||
disable_auto_update |
|
||||
|
|
||||
# 修复签名 |
|
||||
fix_app_signature |
|
||||
|
|
||||
# 最终权限修复 |
|
||||
fix_permissions |
|
||||
|
|
||||
log "INFO" "🎉 完整模式执行完成!" |
|
||||
return 0 |
|
||||
} |
|
||||
|
|
||||
# ==================== 用户界面 ==================== |
|
||||
|
|
||||
# 显示Logo和信息 |
|
||||
show_header() { |
|
||||
clear |
|
||||
echo -e " |
|
||||
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ |
|
||||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ |
|
||||
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ |
|
||||
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ |
|
||||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ |
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ |
|
||||
" |
|
||||
echo -e "${BLUE}================================${NC}" |
|
||||
echo -e "${GREEN}🚀 Cursor 机器码修改工具 (重构版) ${NC}" |
|
||||
echo -e "${YELLOW}📱 关注公众号【煎饼果子卷AI】 ${NC}" |
|
||||
echo -e "${BLUE}================================${NC}" |
|
||||
echo |
|
||||
echo -e "${YELLOW}💡 [免费工具]${NC} 如果对您有帮助,请关注公众号支持开发者" |
|
||||
echo |
|
||||
} |
|
||||
|
|
||||
# 用户选择菜单 |
|
||||
user_menu() { |
|
||||
echo -e "${GREEN}🎯 [选择模式]${NC} 请选择执行模式:" |
|
||||
echo |
|
||||
echo -e "${BLUE} 1️⃣ 快速模式 - 仅修改机器码${NC}" |
|
||||
echo -e "${YELLOW} • 保留现有配置和数据${NC}" |
|
||||
echo -e "${YELLOW} • 执行时间约30秒${NC}" |
|
||||
echo -e "${YELLOW} • 自动权限修复${NC}" |
|
||||
echo |
|
||||
echo -e "${BLUE} 2️⃣ 完整模式 - 重置环境+修改机器码${NC}" |
|
||||
echo -e "${RED} • 删除所有Cursor配置(请备份)${NC}" |
|
||||
echo -e "${YELLOW} • 执行时间约90秒${NC}" |
|
||||
echo -e "${YELLOW} • 彻底重置试用状态${NC}" |
|
||||
echo |
|
||||
|
|
||||
while true; do |
|
||||
read -p "请输入选择 (1 或 2): " choice |
|
||||
case "$choice" in |
|
||||
1) |
|
||||
log "INFO" "✅ 用户选择:快速模式" |
|
||||
return 1 |
|
||||
;; |
|
||||
2) |
|
||||
echo -e "${RED}⚠️ [警告]${NC} 完整模式将删除所有Cursor配置!" |
|
||||
read -p "确认执行?(输入 yes 确认): " confirm |
|
||||
if [ "$confirm" = "yes" ]; then |
|
||||
log "INFO" "✅ 用户选择:完整模式" |
|
||||
return 2 |
|
||||
else |
|
||||
echo -e "${YELLOW}👋 [取消]${NC} 请重新选择" |
|
||||
continue |
|
||||
fi |
|
||||
;; |
|
||||
*) |
|
||||
echo -e "${RED}❌ [错误]${NC} 无效选择,请输入 1 或 2" |
|
||||
;; |
|
||||
esac |
|
||||
done |
|
||||
} |
|
||||
|
|
||||
# 显示完成信息 |
|
||||
show_completion() { |
|
||||
echo |
|
||||
echo -e "${GREEN}================================${NC}" |
|
||||
echo -e "${BLUE} 🎯 执行完成总结 ${NC}" |
|
||||
echo -e "${GREEN}================================${NC}" |
|
||||
echo -e "${GREEN}✅ 机器码配置: 已修改${NC}" |
|
||||
echo -e "${GREEN}✅ 设备识别绕过: 已完成${NC}" |
|
||||
echo -e "${GREEN}✅ 自动更新: 已禁用${NC}" |
|
||||
echo -e "${GREEN}✅ 权限修复: 已完成${NC}" |
|
||||
echo -e "${GREEN}✅ 应用签名: 已修复${NC}" |
|
||||
echo -e "${GREEN}================================${NC}" |
|
||||
echo |
|
||||
echo -e "${YELLOW}📱 关注公众号【煎饼果子卷AI】获取更多Cursor技巧${NC}" |
|
||||
echo |
|
||||
echo -e "${BLUE}🚀 [下一步]${NC} 现在可以启动Cursor使用了!" |
|
||||
echo -e "${BLUE}📄 [日志]${NC} 详细日志保存在: $LOG_FILE" |
|
||||
echo |
|
||||
} |
|
||||
|
|
||||
# ==================== 主函数 ==================== |
|
||||
|
|
||||
main() { |
|
||||
# 检查权限 |
|
||||
if [ "$EUID" -ne 0 ]; then |
|
||||
echo -e "${RED}❌ [错误]${NC} 请使用 sudo 运行此脚本" |
|
||||
echo "示例: sudo $0" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# 检查macOS |
|
||||
if [[ $(uname) != "Darwin" ]]; then |
|
||||
echo -e "${RED}❌ [错误]${NC} 本脚本仅支持 macOS 系统" |
|
||||
exit 1 |
|
||||
fi |
|
||||
|
|
||||
# 初始化 |
|
||||
init_log |
|
||||
log "INFO" "🚀 Cursor重构脚本启动..." |
|
||||
|
|
||||
# 预修复权限 |
|
||||
fix_permissions |
|
||||
|
|
||||
# 显示界面 |
|
||||
show_header |
|
||||
|
|
||||
# 用户选择 |
|
||||
user_menu |
|
||||
local mode=$? |
|
||||
|
|
||||
echo |
|
||||
log "INFO" "🚀 开始执行,请稍候..." |
|
||||
echo |
|
||||
|
|
||||
# 执行对应模式 |
|
||||
case $mode in |
|
||||
1) |
|
||||
if quick_mode; then |
|
||||
show_completion |
|
||||
exit 0 |
|
||||
else |
|
||||
log "ERROR" "❌ 快速模式执行失败" |
|
||||
exit 1 |
|
||||
fi |
|
||||
;; |
|
||||
2) |
|
||||
if full_mode; then |
|
||||
show_completion |
|
||||
exit 0 |
|
||||
else |
|
||||
log "ERROR" "❌ 完整模式执行失败" |
|
||||
exit 1 |
|
||||
fi |
|
||||
;; |
|
||||
esac |
|
||||
} |
|
||||
|
|
||||
# 执行主函数 |
|
||||
main "$@" |
|
1886
scripts/run/cursor_win_id_modifier.ps1
File diff suppressed because it is too large
View File
@ -1,607 +0,0 @@ |
|||||
# 设置输出编码为 UTF-8 |
|
||||
$OutputEncoding = [System.Text.Encoding]::UTF8 |
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 |
|
||||
|
|
||||
# 颜色定义 |
|
||||
$RED = "`e[31m" |
|
||||
$GREEN = "`e[32m" |
|
||||
$YELLOW = "`e[33m" |
|
||||
$BLUE = "`e[34m" |
|
||||
$NC = "`e[0m" |
|
||||
|
|
||||
# 配置文件路径 |
|
||||
$STORAGE_FILE = "$env:APPDATA\Cursor\User\globalStorage\storage.json" |
|
||||
$BACKUP_DIR = "$env:APPDATA\Cursor\User\globalStorage\backups" |
|
||||
|
|
||||
# 新增 Cursor 初始化函数 |
|
||||
function Cursor-初始化 { |
|
||||
Write-Host "$GREEN[信息]$NC 正在执行 Cursor 初始化清理..." |
|
||||
$BASE_PATH = "$env:APPDATA\Cursor\User" |
|
||||
|
|
||||
$filesToDelete = @( |
|
||||
(Join-Path -Path $BASE_PATH -ChildPath "globalStorage\\state.vscdb"), |
|
||||
(Join-Path -Path $BASE_PATH -ChildPath "globalStorage\\state.vscdb.backup") |
|
||||
) |
|
||||
|
|
||||
$folderToCleanContents = Join-Path -Path $BASE_PATH -ChildPath "History" |
|
||||
$folderToDeleteCompletely = Join-Path -Path $BASE_PATH -ChildPath "workspaceStorage" |
|
||||
|
|
||||
Write-Host "$BLUE[调试]$NC 基础路径: $BASE_PATH" |
|
||||
|
|
||||
# 删除指定文件 |
|
||||
foreach ($file in $filesToDelete) { |
|
||||
Write-Host "$BLUE[调试]$NC 检查文件: $file" |
|
||||
if (Test-Path $file) { |
|
||||
try { |
|
||||
Remove-Item -Path $file -Force -ErrorAction Stop |
|
||||
Write-Host "$GREEN[成功]$NC 已删除文件: $file" |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 删除文件 $file 失败: $($_.Exception.Message)" |
|
||||
} |
|
||||
} else { |
|
||||
Write-Host "$YELLOW[警告]$NC 文件不存在,跳过删除: $file" |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# 清空指定文件夹内容 |
|
||||
Write-Host "$BLUE[调试]$NC 检查待清空文件夹: $folderToCleanContents" |
|
||||
if (Test-Path $folderToCleanContents) { |
|
||||
try { |
|
||||
# 获取子项进行删除,以避免删除 History 文件夹本身 |
|
||||
Get-ChildItem -Path $folderToCleanContents -Recurse | Remove-Item -Recurse -Force -ErrorAction Stop |
|
||||
Write-Host "$GREEN[成功]$NC 已清空文件夹内容: $folderToCleanContents" |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 清空文件夹 $folderToCleanContents 内容失败: $($_.Exception.Message)" |
|
||||
} |
|
||||
} else { |
|
||||
Write-Host "$YELLOW[警告]$NC 文件夹不存在,跳过清空: $folderToCleanContents" |
|
||||
} |
|
||||
|
|
||||
# 删除指定文件夹及其内容 |
|
||||
Write-Host "$BLUE[调试]$NC 检查待删除文件夹: $folderToDeleteCompletely" |
|
||||
if (Test-Path $folderToDeleteCompletely) { |
|
||||
try { |
|
||||
Remove-Item -Path $folderToDeleteCompletely -Recurse -Force -ErrorAction Stop |
|
||||
Write-Host "$GREEN[成功]$NC 已删除文件夹: $folderToDeleteCompletely" |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 删除文件夹 $folderToDeleteCompletely 失败: $($_.Exception.Message)" |
|
||||
} |
|
||||
} else { |
|
||||
Write-Host "$YELLOW[警告]$NC 文件夹不存在,跳过删除: $folderToDeleteCompletely" |
|
||||
} |
|
||||
|
|
||||
Write-Host "$GREEN[信息]$NC Cursor 初始化清理完成。" |
|
||||
Write-Host "" # 添加空行以改善输出格式 |
|
||||
} |
|
||||
|
|
||||
# 检查管理员权限 |
|
||||
function Test-Administrator { |
|
||||
$user = [Security.Principal.WindowsIdentity]::GetCurrent() |
|
||||
$principal = New-Object Security.Principal.WindowsPrincipal($user) |
|
||||
return $principal.IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) |
|
||||
} |
|
||||
|
|
||||
if (-not (Test-Administrator)) { |
|
||||
Write-Host "$RED[错误]$NC 请以管理员身份运行此脚本" |
|
||||
Write-Host "请右键点击脚本,选择'以管理员身份运行'" |
|
||||
Read-Host "按回车键退出" |
|
||||
exit 1 |
|
||||
} |
|
||||
|
|
||||
# 显示 Logo |
|
||||
Clear-Host |
|
||||
Write-Host @" |
|
||||
|
|
||||
██████╗██╗ ██╗██████╗ ███████╗ ██████╗ ██████╗ |
|
||||
██╔════╝██║ ██║██╔══██╗██╔════╝██╔═══██╗██╔══██╗ |
|
||||
██║ ██║ ██║██████╔╝███████╗██║ ██║██████╔╝ |
|
||||
██║ ██║ ██║██╔══██╗╚════██║██║ ██║██╔══██╗ |
|
||||
╚██████╗╚██████╔╝██║ ██║███████║╚██████╔╝██║ ██║ |
|
||||
╚═════╝ ╚═════╝ ╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ |
|
||||
|
|
||||
"@ |
|
||||
Write-Host "$BLUE================================$NC" |
|
||||
Write-Host "$GREEN Cursor 设备ID 修改工具 $NC" |
|
||||
Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】 $NC" |
|
||||
Write-Host "$YELLOW 一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" |
|
||||
Write-Host "$YELLOW [重要提示] 本工具免费,如果对您有帮助,请关注公众号【煎饼果子卷AI】 $NC" |
|
||||
Write-Host "" |
|
||||
Write-Host "$YELLOW [小小广告] 出售CursorPro教育号一年质保三个月,有需要找我(86),WeChat:JavaRookie666 $NC" |
|
||||
Write-Host "$BLUE================================$NC" |
|
||||
|
|
||||
# 获取并显示 Cursor 版本 |
|
||||
function Get-CursorVersion { |
|
||||
try { |
|
||||
# 主要检测路径 |
|
||||
$packagePath = "$env:LOCALAPPDATA\\Programs\\cursor\\resources\\app\\package.json" |
|
||||
|
|
||||
if (Test-Path $packagePath) { |
|
||||
$packageJson = Get-Content $packagePath -Raw | ConvertFrom-Json |
|
||||
if ($packageJson.version) { |
|
||||
Write-Host "$GREEN[信息]$NC 当前安装的 Cursor 版本: v$($packageJson.version)" |
|
||||
return $packageJson.version |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# 备用路径检测 |
|
||||
$altPath = "$env:LOCALAPPDATA\\cursor\\resources\\app\\package.json" |
|
||||
if (Test-Path $altPath) { |
|
||||
$packageJson = Get-Content $altPath -Raw | ConvertFrom-Json |
|
||||
if ($packageJson.version) { |
|
||||
Write-Host "$GREEN[信息]$NC 当前安装的 Cursor 版本: v$($packageJson.version)" |
|
||||
return $packageJson.version |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Write-Host "$YELLOW[警告]$NC 无法检测到 Cursor 版本" |
|
||||
Write-Host "$YELLOW[提示]$NC 请确保 Cursor 已正确安装" |
|
||||
return $null |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 获取 Cursor 版本失败: $_" |
|
||||
return $null |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# 获取并显示版本信息 |
|
||||
$cursorVersion = Get-CursorVersion |
|
||||
Write-Host "" |
|
||||
|
|
||||
Write-Host "$YELLOW[重要提示]$NC 最新的 1.0.x (以支持)" |
|
||||
Write-Host "" |
|
||||
|
|
||||
# 检查并关闭 Cursor 进程 |
|
||||
Write-Host "$GREEN[信息]$NC 检查 Cursor 进程..." |
|
||||
|
|
||||
function Get-ProcessDetails { |
|
||||
param($processName) |
|
||||
Write-Host "$BLUE[调试]$NC 正在获取 $processName 进程详细信息:" |
|
||||
Get-WmiObject Win32_Process -Filter "name='$processName'" | |
|
||||
Select-Object ProcessId, ExecutablePath, CommandLine | |
|
||||
Format-List |
|
||||
} |
|
||||
|
|
||||
# 定义最大重试次数和等待时间 |
|
||||
$MAX_RETRIES = 5 |
|
||||
$WAIT_TIME = 1 |
|
||||
|
|
||||
# 处理进程关闭 |
|
||||
function Close-CursorProcess { |
|
||||
param($processName) |
|
||||
|
|
||||
$process = Get-Process -Name $processName -ErrorAction SilentlyContinue |
|
||||
if ($process) { |
|
||||
Write-Host "$YELLOW[警告]$NC 发现 $processName 正在运行" |
|
||||
Get-ProcessDetails $processName |
|
||||
|
|
||||
Write-Host "$YELLOW[警告]$NC 尝试关闭 $processName..." |
|
||||
Stop-Process -Name $processName -Force |
|
||||
|
|
||||
$retryCount = 0 |
|
||||
while ($retryCount -lt $MAX_RETRIES) { |
|
||||
$process = Get-Process -Name $processName -ErrorAction SilentlyContinue |
|
||||
if (-not $process) { break } |
|
||||
|
|
||||
$retryCount++ |
|
||||
if ($retryCount -ge $MAX_RETRIES) { |
|
||||
Write-Host "$RED[错误]$NC 在 $MAX_RETRIES 次尝试后仍无法关闭 $processName" |
|
||||
Get-ProcessDetails $processName |
|
||||
Write-Host "$RED[错误]$NC 请手动关闭进程后重试" |
|
||||
Read-Host "按回车键退出" |
|
||||
exit 1 |
|
||||
} |
|
||||
Write-Host "$YELLOW[警告]$NC 等待进程关闭,尝试 $retryCount/$MAX_RETRIES..." |
|
||||
Start-Sleep -Seconds $WAIT_TIME |
|
||||
} |
|
||||
Write-Host "$GREEN[信息]$NC $processName 已成功关闭" |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# 关闭所有 Cursor 进程 |
|
||||
Close-CursorProcess "Cursor" |
|
||||
Close-CursorProcess "cursor" |
|
||||
|
|
||||
# 执行 Cursor 初始化清理 |
|
||||
# Cursor-初始化 |
|
||||
|
|
||||
# 创建备份目录 |
|
||||
if (-not (Test-Path $BACKUP_DIR)) { |
|
||||
New-Item -ItemType Directory -Path $BACKUP_DIR | Out-Null |
|
||||
} |
|
||||
|
|
||||
# 备份现有配置 |
|
||||
if (Test-Path $STORAGE_FILE) { |
|
||||
Write-Host "$GREEN[信息]$NC 正在备份配置文件..." |
|
||||
$backupName = "storage.json.backup_$(Get-Date -Format 'yyyyMMdd_HHmmss')" |
|
||||
Copy-Item $STORAGE_FILE "$BACKUP_DIR\$backupName" |
|
||||
} |
|
||||
|
|
||||
# 生成新的 ID |
|
||||
Write-Host "$GREEN[信息]$NC 正在生成新的 ID..." |
|
||||
|
|
||||
# 在颜色定义后添加此函数 |
|
||||
function Get-RandomHex { |
|
||||
param ( |
|
||||
[int]$length |
|
||||
) |
|
||||
|
|
||||
$bytes = New-Object byte[] ($length) |
|
||||
$rng = [System.Security.Cryptography.RNGCryptoServiceProvider]::new() |
|
||||
$rng.GetBytes($bytes) |
|
||||
$hexString = [System.BitConverter]::ToString($bytes) -replace '-','' |
|
||||
$rng.Dispose() |
|
||||
return $hexString |
|
||||
} |
|
||||
|
|
||||
# 改进 ID 生成函数 |
|
||||
function New-StandardMachineId { |
|
||||
$template = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx" |
|
||||
$result = $template -replace '[xy]', { |
|
||||
param($match) |
|
||||
$r = [Random]::new().Next(16) |
|
||||
$v = if ($match.Value -eq "x") { $r } else { ($r -band 0x3) -bor 0x8 } |
|
||||
return $v.ToString("x") |
|
||||
} |
|
||||
return $result |
|
||||
} |
|
||||
|
|
||||
# 在生成 ID 时使用新函数 |
|
||||
$MAC_MACHINE_ID = New-StandardMachineId |
|
||||
$UUID = [System.Guid]::NewGuid().ToString() |
|
||||
# 将 auth0|user_ 转换为字节数组的十六进制 |
|
||||
$prefixBytes = [System.Text.Encoding]::UTF8.GetBytes("auth0|user_") |
|
||||
$prefixHex = -join ($prefixBytes | ForEach-Object { '{0:x2}' -f $_ }) |
|
||||
# 生成32字节(64个十六进制字符)的随机数作为 machineId 的随机部分 |
|
||||
$randomPart = Get-RandomHex -length 32 |
|
||||
$MACHINE_ID = "$prefixHex$randomPart" |
|
||||
$SQM_ID = "{$([System.Guid]::NewGuid().ToString().ToUpper())}" |
|
||||
|
|
||||
# 在Update-MachineGuid函数前添加权限检查 |
|
||||
if (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) { |
|
||||
Write-Host "$RED[错误]$NC 请使用管理员权限运行此脚本" |
|
||||
Start-Process powershell "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs |
|
||||
exit |
|
||||
} |
|
||||
|
|
||||
function Update-MachineGuid { |
|
||||
try { |
|
||||
# 检查注册表路径是否存在,不存在则创建 |
|
||||
$registryPath = "HKLM:\SOFTWARE\Microsoft\Cryptography" |
|
||||
if (-not (Test-Path $registryPath)) { |
|
||||
Write-Host "$YELLOW[警告]$NC 注册表路径不存在: $registryPath,正在创建..." |
|
||||
New-Item -Path $registryPath -Force | Out-Null |
|
||||
Write-Host "$GREEN[信息]$NC 注册表路径创建成功" |
|
||||
} |
|
||||
|
|
||||
# 获取当前的 MachineGuid,如果不存在则使用空字符串作为默认值 |
|
||||
$originalGuid = "" |
|
||||
try { |
|
||||
$currentGuid = Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction SilentlyContinue |
|
||||
if ($currentGuid) { |
|
||||
$originalGuid = $currentGuid.MachineGuid |
|
||||
Write-Host "$GREEN[信息]$NC 当前注册表值:" |
|
||||
Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" |
|
||||
Write-Host " MachineGuid REG_SZ $originalGuid" |
|
||||
} else { |
|
||||
Write-Host "$YELLOW[警告]$NC MachineGuid 值不存在,将创建新值" |
|
||||
} |
|
||||
} catch { |
|
||||
Write-Host "$YELLOW[警告]$NC 获取 MachineGuid 失败: $($_.Exception.Message)" |
|
||||
} |
|
||||
|
|
||||
# 创建备份目录(如果不存在) |
|
||||
if (-not (Test-Path $BACKUP_DIR)) { |
|
||||
New-Item -ItemType Directory -Path $BACKUP_DIR -Force | Out-Null |
|
||||
} |
|
||||
|
|
||||
# 创建备份文件(仅当原始值存在时) |
|
||||
if ($originalGuid) { |
|
||||
$backupFile = "$BACKUP_DIR\MachineGuid_$(Get-Date -Format 'yyyyMMdd_HHmmss').reg" |
|
||||
$backupResult = Start-Process "reg.exe" -ArgumentList "export", "`"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography`"", "`"$backupFile`"" -NoNewWindow -Wait -PassThru |
|
||||
|
|
||||
if ($backupResult.ExitCode -eq 0) { |
|
||||
Write-Host "$GREEN[信息]$NC 注册表项已备份到:$backupFile" |
|
||||
} else { |
|
||||
Write-Host "$YELLOW[警告]$NC 备份创建失败,继续执行..." |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# 生成新GUID |
|
||||
$newGuid = [System.Guid]::NewGuid().ToString() |
|
||||
|
|
||||
# 更新或创建注册表值 |
|
||||
Set-ItemProperty -Path $registryPath -Name MachineGuid -Value $newGuid -Force -ErrorAction Stop |
|
||||
|
|
||||
# 验证更新 |
|
||||
$verifyGuid = (Get-ItemProperty -Path $registryPath -Name MachineGuid -ErrorAction Stop).MachineGuid |
|
||||
if ($verifyGuid -ne $newGuid) { |
|
||||
throw "注册表验证失败:更新后的值 ($verifyGuid) 与预期值 ($newGuid) 不匹配" |
|
||||
} |
|
||||
|
|
||||
Write-Host "$GREEN[信息]$NC 注册表更新成功:" |
|
||||
Write-Host "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography" |
|
||||
Write-Host " MachineGuid REG_SZ $newGuid" |
|
||||
return $true |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 注册表操作失败:$($_.Exception.Message)" |
|
||||
|
|
||||
# 尝试恢复备份(如果存在) |
|
||||
if (($backupFile -ne $null) -and (Test-Path $backupFile)) { |
|
||||
Write-Host "$YELLOW[恢复]$NC 正在从备份恢复..." |
|
||||
$restoreResult = Start-Process "reg.exe" -ArgumentList "import", "`"$backupFile`"" -NoNewWindow -Wait -PassThru |
|
||||
|
|
||||
if ($restoreResult.ExitCode -eq 0) { |
|
||||
Write-Host "$GREEN[恢复成功]$NC 已还原原始注册表值" |
|
||||
} else { |
|
||||
Write-Host "$RED[错误]$NC 恢复失败,请手动导入备份文件:$backupFile" |
|
||||
} |
|
||||
} else { |
|
||||
Write-Host "$YELLOW[警告]$NC 未找到备份文件或备份创建失败,无法自动恢复" |
|
||||
} |
|
||||
return $false |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# 创建或更新配置文件 |
|
||||
Write-Host "$GREEN[信息]$NC 正在更新配置..." |
|
||||
|
|
||||
try { |
|
||||
# 检查配置文件是否存在 |
|
||||
if (-not (Test-Path $STORAGE_FILE)) { |
|
||||
Write-Host "$RED[错误]$NC 未找到配置文件: $STORAGE_FILE" |
|
||||
Write-Host "$YELLOW[提示]$NC 请先安装并运行一次 Cursor 后再使用此脚本" |
|
||||
Read-Host "按回车键退出" |
|
||||
exit 1 |
|
||||
} |
|
||||
|
|
||||
# 读取现有配置文件 |
|
||||
try { |
|
||||
$originalContent = Get-Content $STORAGE_FILE -Raw -Encoding UTF8 |
|
||||
|
|
||||
# 将 JSON 字符串转换为 PowerShell 对象 |
|
||||
$config = $originalContent | ConvertFrom-Json |
|
||||
|
|
||||
# 备份当前值 |
|
||||
$oldValues = @{ |
|
||||
'machineId' = $config.'telemetry.machineId' |
|
||||
'macMachineId' = $config.'telemetry.macMachineId' |
|
||||
'devDeviceId' = $config.'telemetry.devDeviceId' |
|
||||
'sqmId' = $config.'telemetry.sqmId' |
|
||||
} |
|
||||
|
|
||||
# 更新特定的值 |
|
||||
$config.'telemetry.machineId' = $MACHINE_ID |
|
||||
$config.'telemetry.macMachineId' = $MAC_MACHINE_ID |
|
||||
$config.'telemetry.devDeviceId' = $UUID |
|
||||
$config.'telemetry.sqmId' = $SQM_ID |
|
||||
|
|
||||
# 将更新后的对象转换回 JSON 并保存 |
|
||||
$updatedJson = $config | ConvertTo-Json -Depth 10 |
|
||||
[System.IO.File]::WriteAllText( |
|
||||
[System.IO.Path]::GetFullPath($STORAGE_FILE), |
|
||||
$updatedJson, |
|
||||
[System.Text.Encoding]::UTF8 |
|
||||
) |
|
||||
Write-Host "$GREEN[信息]$NC 成功更新配置文件" |
|
||||
} catch { |
|
||||
# 如果出错,尝试恢复原始内容 |
|
||||
if ($originalContent) { |
|
||||
[System.IO.File]::WriteAllText( |
|
||||
[System.IO.Path]::GetFullPath($STORAGE_FILE), |
|
||||
$originalContent, |
|
||||
[System.Text.Encoding]::UTF8 |
|
||||
) |
|
||||
} |
|
||||
throw "处理 JSON 失败: $_" |
|
||||
} |
|
||||
# 直接执行更新 MachineGuid,不再询问 |
|
||||
Update-MachineGuid |
|
||||
# 显示结果 |
|
||||
Write-Host "" |
|
||||
Write-Host "$GREEN[信息]$NC 已更新配置:" |
|
||||
Write-Host "$BLUE[调试]$NC machineId: $MACHINE_ID" |
|
||||
Write-Host "$BLUE[调试]$NC macMachineId: $MAC_MACHINE_ID" |
|
||||
Write-Host "$BLUE[调试]$NC devDeviceId: $UUID" |
|
||||
Write-Host "$BLUE[调试]$NC sqmId: $SQM_ID" |
|
||||
|
|
||||
# 显示文件树结构 |
|
||||
Write-Host "" |
|
||||
Write-Host "$GREEN[信息]$NC 文件结构:" |
|
||||
Write-Host "$BLUE$env:APPDATA\Cursor\User$NC" |
|
||||
Write-Host "├── globalStorage" |
|
||||
Write-Host "│ ├── storage.json (已修改)" |
|
||||
Write-Host "│ └── backups" |
|
||||
|
|
||||
# 列出备份文件 |
|
||||
$backupFiles = Get-ChildItem "$BACKUP_DIR\*" -ErrorAction SilentlyContinue |
|
||||
if ($backupFiles) { |
|
||||
foreach ($file in $backupFiles) { |
|
||||
Write-Host "│ └── $($file.Name)" |
|
||||
} |
|
||||
} else { |
|
||||
Write-Host "│ └── (空)" |
|
||||
} |
|
||||
|
|
||||
# 显示公众号信息 |
|
||||
Write-Host "" |
|
||||
Write-Host "$GREEN================================$NC" |
|
||||
Write-Host "$YELLOW 关注公众号【煎饼果子卷AI】一起交流更多Cursor技巧和AI知识(脚本免费、关注公众号加群有更多技巧和大佬) $NC" |
|
||||
Write-Host "$GREEN================================$NC" |
|
||||
Write-Host "" |
|
||||
Write-Host "$GREEN[信息]$NC 请重启 Cursor 以应用新的配置" |
|
||||
Write-Host "" |
|
||||
|
|
||||
# 询问是否要禁用自动更新 |
|
||||
Write-Host "" |
|
||||
Write-Host "$YELLOW[询问]$NC 是否要禁用 Cursor 自动更新功能?" |
|
||||
Write-Host "0) 否 - 保持默认设置 (按回车键)" |
|
||||
Write-Host "1) 是 - 禁用自动更新" |
|
||||
$choice = Read-Host "请输入选项 (0)" |
|
||||
|
|
||||
if ($choice -eq "1") { |
|
||||
Write-Host "" |
|
||||
Write-Host "$GREEN[信息]$NC 正在处理自动更新..." |
|
||||
$updaterPath = "$env:LOCALAPPDATA\cursor-updater" |
|
||||
|
|
||||
# 定义手动设置教程 |
|
||||
function Show-ManualGuide { |
|
||||
Write-Host "" |
|
||||
Write-Host "$YELLOW[警告]$NC 自动设置失败,请尝试手动操作:" |
|
||||
Write-Host "$YELLOW手动禁用更新步骤:$NC" |
|
||||
Write-Host "1. 以管理员身份打开 PowerShell" |
|
||||
Write-Host "2. 复制粘贴以下命令:" |
|
||||
Write-Host "$BLUE命令1 - 删除现有目录(如果存在):$NC" |
|
||||
Write-Host "Remove-Item -Path `"$updaterPath`" -Force -Recurse -ErrorAction SilentlyContinue" |
|
||||
Write-Host "" |
|
||||
Write-Host "$BLUE命令2 - 创建阻止文件:$NC" |
|
||||
Write-Host "New-Item -Path `"$updaterPath`" -ItemType File -Force | Out-Null" |
|
||||
Write-Host "" |
|
||||
Write-Host "$BLUE命令3 - 设置只读属性:$NC" |
|
||||
Write-Host "Set-ItemProperty -Path `"$updaterPath`" -Name IsReadOnly -Value `$true" |
|
||||
Write-Host "" |
|
||||
Write-Host "$BLUE命令4 - 设置权限(可选):$NC" |
|
||||
Write-Host "icacls `"$updaterPath`" /inheritance:r /grant:r `"`$($env:USERNAME):(R)`"" |
|
||||
Write-Host "" |
|
||||
Write-Host "$YELLOW验证方法:$NC" |
|
||||
Write-Host "1. 运行命令:Get-ItemProperty `"$updaterPath`"" |
|
||||
Write-Host "2. 确认 IsReadOnly 属性为 True" |
|
||||
Write-Host "3. 运行命令:icacls `"$updaterPath`"" |
|
||||
Write-Host "4. 确认只有读取权限" |
|
||||
Write-Host "" |
|
||||
Write-Host "$YELLOW[提示]$NC 完成后请重启 Cursor" |
|
||||
} |
|
||||
|
|
||||
try { |
|
||||
# 检查cursor-updater是否存在 |
|
||||
if (Test-Path $updaterPath) { |
|
||||
# 如果是文件,说明已经创建了阻止更新 |
|
||||
if ((Get-Item $updaterPath) -is [System.IO.FileInfo]) { |
|
||||
Write-Host "$GREEN[信息]$NC 已创建阻止更新文件,无需再次阻止" |
|
||||
return |
|
||||
} |
|
||||
# 如果是目录,尝试删除 |
|
||||
else { |
|
||||
try { |
|
||||
Remove-Item -Path $updaterPath -Force -Recurse -ErrorAction Stop |
|
||||
Write-Host "$GREEN[信息]$NC 成功删除 cursor-updater 目录" |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 删除 cursor-updater 目录失败" |
|
||||
Show-ManualGuide |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
# 创建阻止文件 |
|
||||
try { |
|
||||
New-Item -Path $updaterPath -ItemType File -Force -ErrorAction Stop | Out-Null |
|
||||
Write-Host "$GREEN[信息]$NC 成功创建阻止文件" |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 创建阻止文件失败" |
|
||||
Show-ManualGuide |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
# 设置文件权限 |
|
||||
try { |
|
||||
# 设置只读属性 |
|
||||
Set-ItemProperty -Path $updaterPath -Name IsReadOnly -Value $true -ErrorAction Stop |
|
||||
|
|
||||
# 使用 icacls 设置权限 |
|
||||
$result = Start-Process "icacls.exe" -ArgumentList "`"$updaterPath`" /inheritance:r /grant:r `"$($env:USERNAME):(R)`"" -Wait -NoNewWindow -PassThru |
|
||||
if ($result.ExitCode -ne 0) { |
|
||||
throw "icacls 命令失败" |
|
||||
} |
|
||||
|
|
||||
Write-Host "$GREEN[信息]$NC 成功设置文件权限" |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 设置文件权限失败" |
|
||||
Show-ManualGuide |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
# 验证设置 |
|
||||
try { |
|
||||
$fileInfo = Get-ItemProperty $updaterPath |
|
||||
if (-not $fileInfo.IsReadOnly) { |
|
||||
Write-Host "$RED[错误]$NC 验证失败:文件权限设置可能未生效" |
|
||||
Show-ManualGuide |
|
||||
return |
|
||||
} |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 验证设置失败" |
|
||||
Show-ManualGuide |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
Write-Host "$GREEN[信息]$NC 成功禁用自动更新" |
|
||||
} |
|
||||
catch { |
|
||||
Write-Host "$RED[错误]$NC 发生未知错误: $_" |
|
||||
Show-ManualGuide |
|
||||
} |
|
||||
} |
|
||||
else { |
|
||||
Write-Host "$GREEN[信息]$NC 保持默认设置,不进行更改" |
|
||||
} |
|
||||
|
|
||||
# 保留有效的注册表更新 |
|
||||
Update-MachineGuid |
|
||||
|
|
||||
} catch { |
|
||||
Write-Host "$RED[错误]$NC 主要操作失败: $_" |
|
||||
Write-Host "$YELLOW[尝试]$NC 使用备选方法..." |
|
||||
|
|
||||
try { |
|
||||
# 备选方法:使用 Add-Content |
|
||||
$tempFile = [System.IO.Path]::GetTempFileName() |
|
||||
$config | ConvertTo-Json | Set-Content -Path $tempFile -Encoding UTF8 |
|
||||
Copy-Item -Path $tempFile -Destination $STORAGE_FILE -Force |
|
||||
Remove-Item -Path $tempFile |
|
||||
Write-Host "$GREEN[信息]$NC 使用备选方法成功写入配置" |
|
||||
} catch { |
|
||||
Write-Host "$RED[错误]$NC 所有尝试都失败了" |
|
||||
Write-Host "错误详情: $_" |
|
||||
Write-Host "目标文件: $STORAGE_FILE" |
|
||||
Write-Host "请确保您有足够的权限访问该文件" |
|
||||
Read-Host "按回车键退出" |
|
||||
exit 1 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
Write-Host "" |
|
||||
Read-Host "按回车键退出" |
|
||||
exit 0 |
|
||||
|
|
||||
# 在文件写入部分修改 |
|
||||
function Write-ConfigFile { |
|
||||
param($config, $filePath) |
|
||||
|
|
||||
try { |
|
||||
# 使用 UTF8 无 BOM 编码 |
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding $false |
|
||||
$jsonContent = $config | ConvertTo-Json -Depth 10 |
|
||||
|
|
||||
# 统一使用 LF 换行符 |
|
||||
$jsonContent = $jsonContent.Replace("`r`n", "`n") |
|
||||
|
|
||||
[System.IO.File]::WriteAllText( |
|
||||
[System.IO.Path]::GetFullPath($filePath), |
|
||||
$jsonContent, |
|
||||
$utf8NoBom |
|
||||
) |
|
||||
|
|
||||
Write-Host "$GREEN[信息]$NC 成功写入配置文件(UTF8 无 BOM)" |
|
||||
} |
|
||||
catch { |
|
||||
throw "写入配置文件失败: $_" |
|
||||
} |
|
||||
} |
|