├── .editorconfig ├── .eslintignore ├── .eslintrc.cjs ├── .gitattributes ├── .github ├── CODE_OF_CONDUCT.md ├── ISSUE_TEMPLATE │ ├── bug.yml │ ├── config.yml │ └── documentation_related.yml ├── SECURITY.md └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .prettierrc.yaml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── README_zh_CN.md ├── build ├── entitlements.mac.plist ├── icon.icns ├── icon.ico ├── icon.png └── icons │ ├── 1024x1024.png │ ├── 128x128.png │ ├── 16x16.png │ ├── 24x24.png │ ├── 256x256.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 512x512.png │ ├── 64x64.png │ ├── icon.icns │ └── icon.ico ├── dev-app-update.yml ├── docs └── README.md ├── electron-builder.yml ├── electron.vite.config.ts ├── package.json ├── resources ├── icon.png └── workspace │ ├── _machine_ids_reset.py │ ├── browser_utils.py │ ├── cursor_auth_manager.py │ ├── cursor_pro_keep_alive.py │ ├── exit_cursor.py │ ├── get_email_code.py │ ├── logger.py │ ├── requirements.txt │ └── turnstilePatch │ ├── manifest.json │ ├── readme.txt │ └── script.js ├── screenshots ├── alert.png ├── auto_reset.png ├── disable_update.png └── ez2cursor.png ├── src ├── main │ ├── index.ts │ └── types.ts ├── preload │ ├── index.d.ts │ ├── index.ts │ └── types.ts └── renderer │ ├── env.d.ts │ ├── index.html │ └── src │ ├── App.vue │ ├── assets │ ├── base.css │ ├── electron.svg │ ├── main.css │ └── wavy-lines.svg │ ├── components │ └── Versions.vue │ ├── env.d.ts │ ├── main.ts │ ├── types.d.ts │ ├── types.ts │ └── types │ └── electron.d.ts ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.web.json └── workspace ├── _machine_ids_reset.py ├── browser_utils.py ├── cursor_auth_manager.py ├── cursor_pro_keep_alive.py ├── exit_cursor.py ├── get_email_code.py ├── logger.py ├── requirements.txt └── turnstilePatch ├── manifest.json ├── readme.txt └── script.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .gitignore 5 | -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:vue/vue3-recommended', 8 | '@electron-toolkit', 9 | '@electron-toolkit/eslint-config-ts/eslint-recommended', 10 | '@vue/eslint-config-typescript/recommended', 11 | '@vue/eslint-config-prettier' 12 | ], 13 | rules: { 14 | 'vue/require-default-prop': 'off', 15 | 'vue/multi-word-component-names': 'off' 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/.github/CODE_OF_CONDUCT.md -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | name: 遇到问题 2 | description: 关于使用过程中遇到的问题 3 | title: 请填写标题 4 | labels: [bug] 5 | body: 6 | - type: input 7 | validations: 8 | required: true 9 | attributes: 10 | label: "当前系统环境" 11 | placeholder: "win11" 12 | - type: input 13 | validations: 14 | required: true 15 | attributes: 16 | label: "当前版本" 17 | placeholder: "v1.0.0" 18 | - type: textarea 19 | id: other 20 | attributes: 21 | label: "具体信息" 22 | description: "请填写完整的复现步骤和遇到的问题,包括但不限于报错信息、控制台输出、网络请求等" 23 | placeholder: "请填写具体的复现步骤和遇到的问题" 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: 添加功能 4 | url: https://github.com/GalacticDevOps/ez-cursor-free/discussions/new?category=%E6%83%B3%E6%B3%95-ideas 5 | about: 新的功能建议和提问答疑请到讨论区发起 6 | - name: 转到讨论区 7 | url: https://github.com/GalacticDevOps/ez-cursor-free/discussions 8 | about: Issues 用于反馈 Bug, 新的功能建议和提问答疑请到讨论区发起 9 | - name: 提问的艺术 10 | url: https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md 11 | about: 默认所有 Issues 发起者均已了解此处的内容 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation_related.yml: -------------------------------------------------------------------------------- 1 | # 2 | # Licensed to the Apache Software Foundation (ASF) under one or more 3 | # contributor license agreements. See the NOTICE file distributed with 4 | # this work for additional information regarding copyright ownership. 5 | # The ASF licenses this file to You under the Apache License, Version 2.0 6 | # (the "License"); you may not use this file except in compliance with 7 | # the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | 18 | name: Documentation Related 19 | title: "[Doc] Documentation Related " 20 | description: I find some issues related to the documentation. 21 | labels: [documentation] 22 | body: 23 | - type: markdown 24 | attributes: 25 | value: | 26 | For better global communication, Please write in English. 27 | 28 | - type: checkboxes 29 | attributes: 30 | label: Search before asking 31 | description: > 32 | Please make sure to search in the [issues](https://github.com/GalacticDevOps/ez-cursor-free/issues) 33 | first to see whether the same issue was reported already. 34 | options: 35 | - label: > 36 | I had searched in the [issues](https://github.com/GalacticDevOps/ez-cursor-free/issues) and found 37 | no similar issues. 38 | required: true 39 | 40 | - type: textarea 41 | attributes: 42 | label: Documentation Related 43 | description: Describe the suggestion about document. 44 | placeholder: > 45 | e.g There is a typo 46 | validations: 47 | required: true 48 | 49 | - type: checkboxes 50 | attributes: 51 | label: Are you willing to submit PR? 52 | description: > 53 | This is absolutely not required, but we are happy to guide you in the contribution process 54 | especially if you already have a good understanding of how to implement the fix. 55 | options: 56 | - label: Yes I am willing to submit a PR! 57 | 58 | - type: checkboxes 59 | attributes: 60 | label: Code of Conduct 61 | description: > 62 | The Code of Conduct helps create a safe space for everyone. We require that everyone agrees to it.. 63 | options: 64 | - label: I agree to follow this project's [Code of Conduct](https://www.apache.org/foundation/policies/conduct) * 65 | 66 | - type: markdown 67 | attributes: 68 | value: "Thanks for completing our form!" -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security report 2 | 3 | If you find security-related vulnerabilities, please inform us in any of the following ways: 4 | 5 | * Open Issue directly (please hide sensitive information such as site and actual project) 6 | 7 | Thank you very much! -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: cd for ez-cursor-free 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | contents: write 10 | 11 | jobs: 12 | # Windows 构建 13 | build-windows: 14 | name: Build for Windows 15 | runs-on: windows-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | 19 | - name: Setup Node.js 20 | uses: actions/setup-node@v4 21 | with: 22 | node-version: "20.x" 23 | 24 | - name: Setup PNPM 25 | uses: pnpm/action-setup@v2 26 | with: 27 | version: latest 28 | 29 | - name: Install Dependencies 30 | run: pnpm install 31 | 32 | - name: Build Electron App 33 | run: pnpm run build 34 | env: 35 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Build Windows Package 38 | run: pnpm run build:win 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | 42 | - name: List Build Output 43 | run: dir dist 44 | shell: cmd 45 | 46 | - name: Upload Windows Artifacts 47 | uses: softprops/action-gh-release@v1 48 | if: startsWith(github.ref, 'refs/tags/') 49 | with: 50 | files: | 51 | dist/*.* 52 | token: ${{ secrets.GITHUB_TOKEN }} 53 | fail_on_unmatched_files: false 54 | 55 | # Mac 构建 56 | build-macos: 57 | name: Build for macOS 58 | runs-on: macos-latest 59 | steps: 60 | - uses: actions/checkout@v4 61 | 62 | - name: Setup Node.js 63 | uses: actions/setup-node@v4 64 | with: 65 | node-version: "20.x" 66 | 67 | - name: Setup PNPM 68 | uses: pnpm/action-setup@v2 69 | with: 70 | version: latest 71 | 72 | - name: Install Dependencies 73 | run: pnpm install 74 | 75 | # 添加 Rosetta 2 支持 76 | - name: Install Rosetta 2 77 | run: softwareupdate --install-rosetta --agree-to-license 78 | 79 | - name: Build Electron App 80 | run: pnpm run build 81 | env: 82 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 83 | 84 | # 分别构建 Intel 和 ARM 版本 85 | - name: Build macOS Intel Package 86 | run: pnpm run build:mac --x64 87 | env: 88 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 89 | 90 | - name: Build macOS ARM Package 91 | run: pnpm run build:mac --arm64 92 | env: 93 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 94 | 95 | - name: List Build Output 96 | run: ls -la dist 97 | 98 | - name: Upload macOS Artifacts 99 | uses: softprops/action-gh-release@v1 100 | if: startsWith(github.ref, 'refs/tags/') 101 | with: 102 | files: | 103 | dist/*.* 104 | token: ${{ secrets.GITHUB_TOKEN }} 105 | fail_on_unmatched_files: false 106 | 107 | # Linux 构建 108 | build-linux: 109 | name: Build for Linux 110 | runs-on: ubuntu-latest 111 | steps: 112 | - uses: actions/checkout@v4 113 | 114 | - name: Setup Node.js 115 | uses: actions/setup-node@v4 116 | with: 117 | node-version: "20.x" 118 | 119 | # 更新 Ubuntu 软件源 120 | - name: Ubuntu Update with sudo 121 | run: sudo apt-get update 122 | # 安装依赖 123 | - name: Install RPM & Pacman 124 | run: | 125 | sudo apt-get install --no-install-recommends -y rpm && 126 | sudo apt-get install --no-install-recommends -y libarchive-tools && 127 | sudo apt-get install --no-install-recommends -y libopenjp2-tools 128 | 129 | # 安装项目依赖 130 | - name: Install Dependencies 131 | run: npm install 132 | # 构建 Electron App 133 | - name: Build Electron App for Linux 134 | run: npm run build:linux || true 135 | shell: bash 136 | env: 137 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 138 | 139 | # 上传构建产物 140 | - name: Upload Linux Artifacts 141 | uses: softprops/action-gh-release@v1 142 | if: startsWith(github.ref, 'refs/tags/') 143 | with: 144 | files: | 145 | dist/*.* 146 | token: ${{ secrets.GITHUB_TOKEN }} 147 | fail_on_unmatched_files: false 148 | 149 | # 创建 Release 150 | create-release: 151 | needs: [build-windows, build-macos, build-linux] 152 | runs-on: ubuntu-latest 153 | steps: 154 | - uses: actions/checkout@v4 155 | with: 156 | fetch-depth: 0 # 获取完整的 git 历史记录 157 | 158 | - name: Generate Release Notes 159 | id: release_notes 160 | run: | 161 | VERSION=${GITHUB_REF#refs/tags/v} 162 | echo "Processing version: $VERSION" 163 | 164 | # 首先尝试从 CHANGELOG.md 获取 165 | if [ -f CHANGELOG.md ]; then 166 | CHANGES=$(awk -v ver="$VERSION" ' 167 | BEGIN { found=0; content="" } 168 | /^## \[?'$VERSION'\]?/ { found=1; next } 169 | /^## \[?[0-9]+\.[0-9]+\.[0-9]+/ { if (found) exit } 170 | { if (found) content = content $0 "\n" } 171 | END { printf "%s", content } 172 | ' CHANGELOG.md) 173 | fi 174 | 175 | # 如果 CHANGELOG 中没有找到,则使用 git log 176 | if [ -z "$CHANGES" ]; then 177 | echo "No changelog entry found, using git log..." 178 | PREV_TAG=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") 179 | if [ -n "$PREV_TAG" ]; then 180 | CHANGES=$(git log --pretty=format:"* %s" $PREV_TAG..HEAD) 181 | else 182 | CHANGES=$(git log --pretty=format:"* %s" -n 10) 183 | fi 184 | fi 185 | 186 | # 如果还是空的,使用默认消息 187 | if [ -z "$CHANGES" ]; then 188 | CHANGES="Release version $VERSION" 189 | fi 190 | 191 | # 添加版本标题 192 | FINAL_NOTES="## Release v$VERSION\n\n$CHANGES" 193 | 194 | # 输出到 GITHUB_OUTPUT 195 | { 196 | echo "CHANGES<> $GITHUB_OUTPUT 200 | 201 | # 调试输出 202 | echo "Generated release notes:" 203 | echo -e "$FINAL_NOTES" 204 | shell: bash 205 | 206 | - name: Update Release 207 | uses: softprops/action-gh-release@v2 208 | if: startsWith(github.ref, 'refs/tags/') 209 | with: 210 | name: Release ${{ github.ref_name }} 211 | body: ${{ steps.release_notes.outputs.CHANGES }} 212 | draft: false 213 | prerelease: false 214 | token: ${{ secrets.GITHUB_TOKEN }} 215 | generate_release_notes: false # 不使用 GitHub 自动生成的发布说明 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | out 4 | .DS_Store 5 | *.log* 6 | /.vscode 7 | pnpm-lock.yaml 8 | /.idea 9 | /workspace/__pycache__ 10 | /resources/workspace/__pycache__ 11 | cursor_accounts.txt 12 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com 2 | disturl=https://registry.npmmirror.com/-/binary/node 3 | electron_mirror=https://cdn.npmmirror.com/binaries/electron/ 4 | electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/ 5 | shamefully-hoist=true -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | out 2 | dist 3 | pnpm-lock.yaml 4 | LICENSE.md 5 | tsconfig.json 6 | tsconfig.*.json 7 | -------------------------------------------------------------------------------- /.prettierrc.yaml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | semi: false 3 | printWidth: 100 4 | trailingComma: none 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [1.1.0] - 2024-01-16 9 | 10 | ### Added 11 | - Auto Restart Cursor Process 12 | - Auto Reset Machine Ids 13 | 14 | ### Fixed 15 | - Fix some known issues 16 | 17 | ## [1.0.9] - 2024-01-15 18 | 19 | ### Fixed 20 | - Fix some known issues 21 | 22 | ## [1.0.8-alpha1] - 2024-01-15 23 | 24 | ### Fixed 25 | - Fix some known issues 26 | 27 | ## [1.0.8] - 2024-01-14 28 | 29 | ### Fixed 30 | - Fix some known issues 31 | 32 | ### Pending 33 | - Email problem for receiving emails 34 | 35 | ## [1.0.3] - 2024-01-10 36 | 37 | ### Added 38 | - 新增 Python 环境自动检测功能 39 | - 新增依赖包自动安装功能 40 | - 新增环境配置引导功能 41 | 42 | ### Changed 43 | - 优化 Python 环境检测逻辑 44 | - 改进错误提示信息 45 | - 优化依赖安装流程 46 | 47 | ### Fixed 48 | - 修复 Python 环境检测问题 49 | - 修复依赖安装失败的问题 50 | - 修复一些已知的稳定性问题 51 | 52 | ## [1.0.2] - 2024-01-10 53 | 54 | ### Added 55 | - 新增一键重置功能 56 | - 优化界面布局和交互体验 57 | - 改进错误提示和状态反馈 58 | 59 | ### Changed 60 | - 优化按钮布局和样式 61 | - 改进功能区域的组织方式 62 | - 简化操作流程 63 | 64 | ### Fixed 65 | - 修复中文显示乱码问题 66 | - 修复插件加载相关问题 67 | - 修复一些已知的界面问题 68 | 69 | ## [1.0.1-beta3] - 2024-12-26 70 | 71 | ### Fixed 72 | - 修复一些已知的问题 73 | - linux下目前已知自动打包后还缺少snap包,需要手动打包 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 dacrab 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🚀 Ez2 Use Cursor Free Tools 2 | 3 | ⚠️ This tool is only available to those who are unable to pay but urgently need to use it. 4 | 5 |
6 | 7 | 8 | 9 | 10 | 11 | [![Stars](https://img.shields.io/github/stars/GalacticDevOps/ez-cursor-free?style=flat-square&logo=github)](https://github.com/GalacticDevOps/ez-cursor-free/stargazers) 12 | [![Build Release](https://github.com/GalacticDevOps/ez-cursor-free/actions/workflows/release.yml/badge.svg)](https://github.com/GalacticDevOps/ez-cursor-free/actions/workflows/release.yml) 13 | [![Issues](https://img.shields.io/github/issues/GalacticDevOps/ez-cursor-free)](https://github.com/GalacticDevOps/ez-cursor-free/issues) 14 | 15 | [🌏 中文](README_zh_CN.md) | [🌟 English](README.md) 16 | 17 | Cursor Logo 18 | 19 |
20 | 21 | ## Screenshots 22 | 23 |
24 | 25 | ![alt text](/screenshots/ez2cursor.png) 26 | 27 |
28 | 29 | 30 | > ⚠️ **IMPORTANT NOTICE** 31 | > 32 | > This tool currently supports: 33 | > - ✅ Latest v0.45.x versions and below 34 | > - 🎯 [Cursor AI IDE Changelog](https://www.cursor.com/changelog) 35 | 36 | ## Latest Version 37 | 38 | - Mac https://downloader.cursor.sh/mac/installer/universal 39 | - Windows x64 https://downloader.cursor.sh/windows/nsis/x64 40 | - Linux x64 https://downloader.cursor.sh/linux/appImage/x64 41 | 42 | > 💾 **Download Cursor Hisotry(v0.44.x and below) Version** 43 | > 44 | > https://downloader-cursor.deno.dev/history/ 45 | 46 | ## Download Links History 47 | 48 | | Version | Date | Mac Installer | Windows X64 Installer | Linux X64 Installer | 49 | | --- | --- | --- | --- | --- | 50 | | 0.45.9 | 2025-02-04 | [Link](https://downloader.cursor.sh/builds/250202tgstl42dt/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250202tgstl42dt/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250202tgstl42dt/linux/appImage/x64) | 51 | | 0.45.8 | 2025-02-02 | [Link](https://downloader.cursor.sh/builds/250201b44xw1x2k/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250201b44xw1x2k/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250201b44xw1x2k/linux/appImage/x64) | 52 | | 0.45.7 | 2025-01-31 | [Link](https://downloader.cursor.sh/builds/250130nr6eorv84/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250130nr6eorv84/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250130nr6eorv84/linux/appImage/x64) | 53 | | 0.45.5 | 2025-01-29 | [Link](https://downloader.cursor.sh/builds/250128loaeyulq8/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250128loaeyulq8/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250128loaeyulq8/linux/appImage/x64) | 54 | | 0.45.4 | 2025-01-27 | [Link](https://downloader.cursor.sh/builds/250126vgr3vztvj/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250126vgr3vztvj/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250126vgr3vztvj/linux/appImage/x64) | 55 | | 0.45.3 | 2025-01-25 | [Link](https://downloader.cursor.sh/builds/250124b0rcj0qql/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250124b0rcj0qql/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250124b0rcj0qql/linux/appImage/x64) | 56 | | 0.45.2 | 2025-01-24 | [Link](https://downloader.cursor.sh/builds/250123mhituoa6o/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250123mhituoa6o/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250123mhituoa6o/linux/appImage/x64) | 57 | | 0.44.11 | 2025-01-04 | [Link](https://downloader.cursor.sh/builds/250103fqxdt5u9z/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250103fqxdt5u9z/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250103fqxdt5u9z/linux/appImage/x64) | 58 | | 0.44.9 | 2024-12-28 | [Link](https://downloader.cursor.sh/builds/2412268nc6pfzgo/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/2412268nc6pfzgo/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/2412268nc6pfzgo/linux/appImage/x64) | 59 | | 0.44.8 | 2024-12-23 | [Link](https://downloader.cursor.sh/builds/241222ooktny8mh/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/241222ooktny8mh/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/241222ooktny8mh/linux/appImage/x64) | 60 | | 0.43.6 | 2024-12-06 | [Link](https://downloader.cursor.sh/builds/241206z7j6me2e2/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/241206z7j6me2e2/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/241206z7j6me2e2/linux/appImage/x64) | 61 | | 0.42.5 | - | [Link](https://downloader.cursor.sh/builds/24111460bf2loz1/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/24111460bf2loz1/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/24111460bf2loz1/linux/appImage/x64) | 62 | | 0.42.4 | - | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/linux/appImage/x64) | 63 | | 0.42.3 | - | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/linux/appImage/x64) | 64 | 65 | 66 |
67 | 🔒 Disable Auto Update 68 | 69 | > To prevent Cursor from automatically updating to unsupported new versions, you can choose to disable the auto-update feature. 70 | 71 | ### Method 1: Click the "Disable Auto Update" Button (Recommended) 72 | 73 | ![alt text](/screenshots/disable_update.png) 74 | 75 | ### Method 2: Manual Disable 76 | 77 | **Windows:** 78 | 1. Close all Cursor processes 79 | 2. Delete directory: `%LOCALAPPDATA%\cursor-updater` 80 | 3. Create a file with the same name (without extension) in the same location 81 | 82 | **macOS:** 83 | ```bash 84 | # Close Cursor 85 | pkill -f "Cursor" 86 | # Remove update directory and create blocking file 87 | rm -rf ~/Library/Application\ Support/cursor-updater 88 | touch ~/Library/Application\ Support/cursor-updater 89 | ``` 90 | 91 | **Linux:** 92 | ```bash 93 | # Close Cursor 94 | pkill -f "Cursor" 95 | # Remove update directory and create blocking file 96 | rm -rf ~/.config/cursor-updater 97 | touch ~/.config/cursor-updater 98 | ``` 99 | 100 | > ⚠️ **Note:** After disabling automatic updates, you will need to manually download and install new versions. It is recommended to confirm that the new version is compatible before updating. 101 | 102 | 103 |
104 | 105 | > ⚠️ **NOTICE** 106 | > 107 | > The project requires a python environment to run. Please ensure that python3.8 and above are installed. 108 | > 109 | > The program requires Google Chrome browser, please make sure to install Google Chrome browser 110 | 111 | If you don’t have Google Chrome, you can download Google Chrome from [here](https://www.google.com/intl/en_pk/chrome/), Or you can choose to use another browser. For details on how to use it, see [here](https://github.com/GalacticDevOps/ez-cursor-free/issues/10) 112 | 113 |
114 | 115 | ![alt text](/screenshots/alert.png) 116 | 117 | If you encounter this problem, please click [here](https://sysin.org/blog/macos-if-crashes-when-opening/) to view the solution 118 | 119 |
120 | 121 | ### 📝 Description 122 | 123 | > When you encounter any of these messages: 124 | 125 | #### Issue 1: Trial Account Limit

Back To Top

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

Back To Top

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

153 | 154 | ### Solution - Trial Account Limit 155 | 156 | #### Step 1: Quick Reset (Recommended) 157 | 158 | 1. Close Cursor application 159 | 2. Run the `ez-cursor-free` (see installation instructions below) 160 | 3. Reopen Cursor to continue using 161 | 162 | #### Step 2: Use ez-cursor-free, Click the "Auto Reset" Button 163 | 164 | ![alt text](/screenshots/auto_reset.png) 165 | 166 | > Wait for the automatic reset to complete. If the waiting time is too long, it may be due to network problems. Please see solution 3. 167 | 168 | #### Step 3: Network Optimization 169 | 170 | If the above solutions don't work, try: 171 | 172 | - Switch to low-latency nodes (Recommended regions: Japan, Singapore, US, Hong Kong) 173 | - Ensure network stability 174 | - Clear browser cache and retry 175 | 176 |

177 | 178 | ### Solution - API Key Limitation 179 | 180 | #### Step: Uninstall Cursor Completely And Reinstall (API key Issue) 181 | 182 | 1. Download [HiBit Uninstaller.exe](https://hibitsoft.ir/Uninstaller.html) 183 | 2. Uninstall Cursor app completely 184 | 3. Re-Install Cursor app 185 | 4. Continue to Solution 1 186 | 187 | 188 | ### 💻 System Support 189 | 190 |
191 | 192 | **Windows** ✅ 193 | **macOS** ✅ 194 | **Linux** ✅ 195 |
196 | 197 | ## Installation 198 | 199 | 1. Download the latest release from the [Releases](https://github.com/GalacticDevOps/ez-cursor-free/releases) page 200 | 2. Install and run the application 201 | 3. Use the features as needed 202 | 4. Enjoy your free trip 203 | 204 | ### 📦 Manual Installation 205 | 206 | > Download the appropriate file for your system from [releases](https://github.com/GalacticDevOps/ez-cursor-free/releases/latest) 207 | 208 | ### 🎉 Thanks 209 | 210 | This project is developed based on the [gpt-cursor-auto](https://github.com/hmhm2022/gpt-cursor-auto) project, thank you for the contribution of the original author [@hmhm2022](https://github.com/hmhm2022). 211 | 212 | ## 🧿 Contact Information 213 | 214 |
215 | 216 | [![Discord](https://img.shields.io/discord/1336288519836012608?label=Discord&logo=discord&style=plastic)](https://discord.gg/ZvC5NZdUBE) 217 | 218 |
219 | 220 | ## ⭐ Star History 221 | 222 | [![Star History Chart](https://api.star-history.com/svg?repos=GalacticDevOps/ez-cursor-free&type=Date)](https://star-history.com/#GalacticDevOps/ez-cursor-free&Date) 223 | 224 | ![Alt](https://repobeats.axiom.co/api/embed/e093d035f86c7cc57f690c634df67bcf821455df.svg "Repobeats analytics image") 225 | 226 | ## Contributors 227 | 228 | Thanks to the following people who contributed code to ez-cursor-free! 229 | 230 | 231 | 232 | 233 | 234 | ## 📄 License 235 | 236 |
237 | MIT License 238 | 239 | Copyright (c) 2024 240 | 241 | Permission is hereby granted, free of charge, to any person obtaining a copy 242 | of this software and associated documentation files (the "Software"), to deal 243 | in the Software without restriction, including without limitation the rights 244 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 245 | copies of the Software, and to permit persons to whom the Software is 246 | furnished to do so, subject to the following conditions: 247 | 248 | The above copyright notice and this permission notice shall be included in all 249 | copies or substantial portions of the Software. 250 | 251 |
-------------------------------------------------------------------------------- /README_zh_CN.md: -------------------------------------------------------------------------------- 1 | # 🚀 Ez2 Use Cursor Free Tools 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | [![Stars](https://img.shields.io/github/stars/GalacticDevOps/ez-cursor-free?style=flat-square&logo=github)](https://github.com/GalacticDevOps/ez-cursor-free/stargazers) 10 | [![Build Release](https://github.com/GalacticDevOps/ez-cursor-free/actions/workflows/release.yml/badge.svg)](https://github.com/GalacticDevOps/ez-cursor-free/actions/workflows/release.yml) 11 | [![Issues](https://img.shields.io/github/issues/GalacticDevOps/ez-cursor-free)](https://github.com/GalacticDevOps/ez-cursor-free/issues) 12 | 13 | [🌏 中文](README_zh_CN.md) | [🌟 English](README.md) 14 | 15 | Cursor Logo 16 | 17 |
18 | 19 | ## Screenshots 20 | 21 |
22 | 23 | ![alt text](/screenshots/ez2cursor.png) 24 | 25 |
26 | 27 | 28 | > ⚠️ **重要提示** 29 | > 30 | > This tool currently supports: 31 | > - ✅ Latest v0.45.x versions and below 32 | > - 🎯 [Cursor AI IDE Changelog](https://www.cursor.com/changelog) 33 | 34 | ## 最新版本 35 | 36 | - Mac https://downloader.cursor.sh/mac/installer/universal 37 | - Windows x64 https://downloader.cursor.sh/windows/nsis/x64 38 | - Linux x64 https://downloader.cursor.sh/linux/appImage/x64 39 | 40 | > 💾 **下载Cursor历史版本(v0.44.x及以下)** 41 | > 42 | > https://downloader-cursor.deno.dev/history/ 43 | 44 | ## 下载链接历史 45 | 46 | | 版本 | 日期 | Mac | 64位Windows | 64位Linux | 47 | | --- | --- | --- | --- | --- | 48 | | 0.45.9 | 2025-02-04 | [Link](https://downloader.cursor.sh/builds/250202tgstl42dt/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250202tgstl42dt/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250202tgstl42dt/linux/appImage/x64) | 49 | | 0.45.8 | 2025-02-02 | [Link](https://downloader.cursor.sh/builds/250201b44xw1x2k/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250201b44xw1x2k/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250201b44xw1x2k/linux/appImage/x64) | 50 | | 0.45.7 | 2025-01-31 | [Link](https://downloader.cursor.sh/builds/250130nr6eorv84/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250130nr6eorv84/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250130nr6eorv84/linux/appImage/x64) | 51 | | 0.45.5 | 2025-01-29 | [Link](https://downloader.cursor.sh/builds/250128loaeyulq8/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250128loaeyulq8/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250128loaeyulq8/linux/appImage/x64) | 52 | | 0.45.4 | 2025-01-27 | [Link](https://downloader.cursor.sh/builds/250126vgr3vztvj/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250126vgr3vztvj/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250126vgr3vztvj/linux/appImage/x64) | 53 | | 0.45.3 | 2025-01-25 | [Link](https://downloader.cursor.sh/builds/250124b0rcj0qql/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250124b0rcj0qql/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250124b0rcj0qql/linux/appImage/x64) | 54 | | 0.45.2 | 2025-01-24 | [Link](https://downloader.cursor.sh/builds/250123mhituoa6o/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250123mhituoa6o/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250123mhituoa6o/linux/appImage/x64) | 55 | | 0.44.11 | 2025-01-04 | [Link](https://downloader.cursor.sh/builds/250103fqxdt5u9z/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/250103fqxdt5u9z/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/250103fqxdt5u9z/linux/appImage/x64) | 56 | | 0.44.9 | 2024-12-28 | [Link](https://downloader.cursor.sh/builds/2412268nc6pfzgo/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/2412268nc6pfzgo/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/2412268nc6pfzgo/linux/appImage/x64) | 57 | | 0.44.8 | 2024-12-23 | [Link](https://downloader.cursor.sh/builds/241222ooktny8mh/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/241222ooktny8mh/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/241222ooktny8mh/linux/appImage/x64) | 58 | | 0.43.6 | 2024-12-06 | [Link](https://downloader.cursor.sh/builds/241206z7j6me2e2/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/241206z7j6me2e2/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/241206z7j6me2e2/linux/appImage/x64) | 59 | | 0.42.5 | - | [Link](https://downloader.cursor.sh/builds/24111460bf2loz1/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/24111460bf2loz1/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/24111460bf2loz1/linux/appImage/x64) | 60 | | 0.42.4 | - | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/linux/appImage/x64) | 61 | | 0.42.3 | - | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/mac/installer/universal) | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/windows/nsis/x64) | [Link](https://downloader.cursor.sh/builds/230313mzl4w4u92/linux/appImage/x64) | 62 | 63 | 64 |
65 | 🔒 禁用自动更新 66 | 67 | > 为了防止 Cursor 自动更新到不受支持的新版本,您可以选择禁用自动更新功能。 68 | 69 | ### 方法 1: 点击`禁用更新`按钮 (推荐) 70 | 71 | ![alt text](/screenshots/disable_update.png) 72 | 73 | ### 方法 2: 手动禁用 74 | 75 | **Windows:** 76 | 1. 关闭所有Cursor进程 77 | 2. 删除目录: `%LOCALAPPDATA%\cursor-updater` 78 | 3. 在相同位置创建同一文件夹并设置为只读 79 | 80 | **macOS:** 81 | ```bash 82 | # Close Cursor 83 | pkill -f "Cursor" 84 | # 删除更新目录并创建重复文件夹 85 | rm -rf ~/Library/Application\ Support/cursor-updater 86 | touch ~/Library/Application\ Support/cursor-updater 87 | ``` 88 | 89 | **Linux:** 90 | ```bash 91 | # 关闭Cursor 92 | pkill -f "Cursor" 93 | # 删除更新目录并创建重复文件夹 94 | rm -rf ~/.config/cursor-updater 95 | touch ~/.config/cursor-updater 96 | ``` 97 | 98 | > ⚠️ **Note:** 禁用自动更新后,您将需要手动下载并安装新版本。建议更新前确认新版本兼容。 99 | 100 | 101 |
102 | 103 | > ⚠️ **警告** 104 | > 105 | > 需要python环境才能运行。请确保安装了python3.8及以上版本。 106 | > 107 | > 需要安装Google Chrome浏览器,请确保安装Google Chrome浏览器 108 | 109 | 如果您没有 Google Chrome,您可以从[此处](https://www.google.com/intl/en_pk/chrome/)下载 Google Chrome,或者您也可以选择使用其他浏览器。有关如何使用它的详细信息,请参阅[此处](https://github.com/GalacticDevOps/ez-cursor-free/issues/10) 110 | 111 |
112 | 113 | ![alt text](/screenshots/alert.png) 114 | 115 | 如果您遇到此问题,请点击[此处](https://sysin.org/blog/macos-if-crashes-when-opening/)查看解决方案 116 |
117 | 118 | ### 📝 问题描述 119 | 120 | > 当你遇到以下提示信息: 121 | 122 | #### 问题 1: Trial Account Limit

Back To Top

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

Back To Top

132 | 133 | ```text 134 | [New Issue] 135 | 136 | Composer relies on custom models that cannot be billed to an API key. 137 | Please disable API keys and use a Pro or Business subscription. 138 | Request ID: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 139 | ``` 140 | 141 | #### 问题 3: Trial Request Limit 142 | 143 | > 这表明您已达到 `Pro Trail` 免费试用期内的使用限制: 144 | 145 | ```text 146 | You've reached your trial request limit. 147 | ``` 148 | 149 |

150 | 151 | ### 解决方案1 - Trial Account Limit 152 | 153 | #### 步骤 1: 快速重置 (推荐) 154 | 155 | 1. 关闭`Cursor`(您也可以不关闭,程序自带关闭和重启激活功能) 156 | 2. 运行`ez-cursor-free` (安装详情在下方) 157 | 3. 重新打开`Cursor`并使用 158 | 159 | #### 步骤 2: 使用ez-cursor-free,点击一键重置功能 160 | 161 | ![alt text](/screenshots/auto_reset.png) 162 | 163 | > 等待自动重置完成。如果等待时间过长,可能是由于网络问题。请参阅步骤 3。 164 | 165 | #### 步骤 3: Network Optimization 166 | 167 | 如果上述解决方案不起作用,请尝试: 168 | 169 | - 切换至低延迟节点(推荐地区:日本、新加坡、美国、香港) 170 | - 确保网络稳定 171 | - 清除浏览器缓存并重试 172 | 173 |

174 | 175 | ### 解决方案2 - API Key Limitation 176 | 177 | #### 步骤: Uninstall Cursor Completely And Reinstall (API key Issue) 178 | 179 | 1. 下载 [HiBit Uninstaller.exe](https://hibitsoft.ir/Uninstaller.html) 180 | 2. 彻底卸载`Cursor` 181 | 3. 重新安装`Cursor` 182 | 4. 回到解决方案1 183 | 184 | 185 | ### 💻 支持的系统 186 | 187 |
188 | 189 | **Windows** ✅ 190 | **macOS** ✅ 191 | **Linux** ✅ 192 |
193 | 194 | ## 安装 195 | 196 | 1. 点击[这里](https://github.com/GalacticDevOps/ez-cursor-free/releases),从打开的页面中下载最新版本 197 | 2. 安装并运行应用程序 198 | 3. 根据需要使用功能 199 | 4. 尽情享受吧! 200 | 201 | ### 📦 手动安装 202 | 203 | > 点击[这里](https://github.com/GalacticDevOps/ez-cursor-free/releases/latest),找到符合您当前系统的安装包 204 | 205 | ### 🎉 致谢 206 | 207 | 本项目基于[gpt-cursor-auto](https://github.com/hmhm2022/gpt-cursor-auto)项目开发,感谢原作者的贡献[@hmhm2022](https://github.com/hmhm2022). 208 | 209 | ## 🧿 联系方式 210 | 211 |
212 | 213 | 214 | 215 |
216 | 217 | 218 | 219 | ## ⭐ 项目统计 220 | 221 | [![Star History Chart](https://api.star-history.com/svg?repos=GalacticDevOps/ez-cursor-free&type=Date)](https://star-history.com/#GalacticDevOps/ez-cursor-free&Date) 222 | 223 | ![Alt](https://repobeats.axiom.co/api/embed/e093d035f86c7cc57f690c634df67bcf821455df.svg "Repobeats analytics image") 224 | 225 | ## 贡献者 226 | 227 | 感谢以下为ez-cursor-free贡献代码的人! 228 | 229 | 230 | 231 | 232 | 233 | ## 📄 许可证 234 | 235 |
236 | MIT 许可证 237 | 238 | Copyright (c) 2024 239 | 240 | Permission is hereby granted, free of charge, to any person obtaining a copy 241 | of this software and associated documentation files (the "Software"), to deal 242 | in the Software without restriction, including without limitation the rights 243 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 244 | copies of the Software, and to permit persons to whom the Software is 245 | furnished to do so, subject to the following conditions: 246 | 247 | The above copyright notice and this permission notice shall be included in all 248 | copies or substantial portions of the Software. 249 | 250 |
-------------------------------------------------------------------------------- /build/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.cs.allow-dyld-environment-variables 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /build/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icon.icns -------------------------------------------------------------------------------- /build/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icon.ico -------------------------------------------------------------------------------- /build/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icon.png -------------------------------------------------------------------------------- /build/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/1024x1024.png -------------------------------------------------------------------------------- /build/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/128x128.png -------------------------------------------------------------------------------- /build/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/16x16.png -------------------------------------------------------------------------------- /build/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/24x24.png -------------------------------------------------------------------------------- /build/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/256x256.png -------------------------------------------------------------------------------- /build/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/32x32.png -------------------------------------------------------------------------------- /build/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/48x48.png -------------------------------------------------------------------------------- /build/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/512x512.png -------------------------------------------------------------------------------- /build/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/64x64.png -------------------------------------------------------------------------------- /build/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/icon.icns -------------------------------------------------------------------------------- /build/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/build/icons/icon.ico -------------------------------------------------------------------------------- /dev-app-update.yml: -------------------------------------------------------------------------------- 1 | provider: generic 2 | url: https://example.com/auto-updates 3 | updaterCacheDirName: ez-cursor-free-updater 4 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## IPC 通信 2 | 项目使用 Electron 的 IPC 机制进行主进程和渲染进程间的通信。所有 IPC 通道都在 `src/renderer/src/types/electron.d.ts` 中定义了类型,确保类型安全。 3 | 4 | ### 主要功能 5 | - 检查和设置文件权限 6 | - 修改 ID 7 | - 备份和恢复存储数据 8 | - 检查软件更新 9 | - 打开指定页面 10 | 11 | ## 安装与启动 12 | 13 | ```bash 14 | git clone https://github.com/GalacticDevOps/ez-cursor-free.git 15 | cd ez-cursor-free 16 | pnpm install 17 | pnpm run dev 18 | ``` 19 | 20 | ### 可用的 IPC 通道: 21 | - `check-file-permission`:检查文件是否为只读。 22 | ```typescript 23 | 返回类型:Promise<{ isReadOnly: boolean }> 24 | ``` 25 | 26 | - `modify-ids`:修改 ID,并返回修改结果。 27 | ```typescript 28 | 返回类型:Promise 29 | ``` 30 | 31 | - `set-file-permission`:设置文件的权限。 32 | ```typescript 33 | 参数:value: boolean 34 | 返回类型:Promise 35 | ``` 36 | 37 | - `get-current-ids`:获取当前的 IDs。 38 | ```typescript 39 | 返回类型:Promise 40 | ``` 41 | 42 | - `backup-storage`:执行备份存储操作。 43 | ```typescript 44 | 返回类型:Promise 45 | ``` 46 | 47 | - `restore-storage`:恢复存储数据。 48 | ```typescript 49 | 返回类型:Promise 50 | ``` 51 | 52 | - `check-backup-exists`:检查备份是否存在。 53 | ```typescript 54 | 返回类型:Promise<{ exists: boolean }> 55 | ``` 56 | 57 | - `check-updates`:检查是否有可用的更新。 58 | ```typescript 59 | 返回类型:Promise<{ hasUpdate: boolean, latestVersion: string, downloadUrl: string, releaseNotes: string } | null> 60 | ``` 61 | 62 | - `open-release-page`:打开指定页面。 63 | ```typescript 64 | 参数:url: string 65 | 返回类型:Promise<{ success: boolean }> 66 | ``` 67 | 68 | ### 示例代码 69 | 调用 check-file-permission 通道: 70 | ```typescript 71 | window.electron.ipcRenderer.invoke('check-file-permission').then(result => { 72 | console.log(result.isReadOnly); // 输出文件是否只读 73 | }); 74 | ``` 75 | 76 | ### 贡献 77 | 欢迎贡献代码! 78 | 79 | 如果你有好的想法或者发现问题,欢迎通过以下方式参与贡献: 80 | 81 | 提交 [Issues](https://github.com/GalacticDevOps/ez-cursor-free/issues) 82 | 83 | 提交 [Pull Requests](https://github.com/GalacticDevOps/ez-cursor-free/pulls) 来修复问题或添加新功能。 -------------------------------------------------------------------------------- /electron-builder.yml: -------------------------------------------------------------------------------- 1 | appId: com.ez2cursor.app 2 | productName: ez-cursor-free 3 | copyright: Copyright © Soleil 2025 4 | directories: 5 | buildResources: build 6 | files: 7 | - 'workspace/**' 8 | - 'out/**' 9 | - '!**/.vscode/*' 10 | - '!src/*' 11 | - '!electron.vite.config.{js,ts,mjs,cjs}' 12 | - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}' 13 | - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}' 14 | - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}' 15 | asar: true 16 | asarUnpack: 17 | - resources/** 18 | extraResources: 19 | - from: "workspace" 20 | to: "workspace" 21 | filter: 22 | - "**/*" 23 | win: 24 | executableName: ez-cursor-free 25 | icon: build/icons/512x512.png 26 | # 构建类型 27 | target: 28 | # 安装版 29 | - nsis 30 | # 打包版 31 | - portable 32 | nsis: 33 | oneClick: false 34 | artifactName: ${name}-${version}-setup.${ext} 35 | shortcutName: ${productName} 36 | uninstallDisplayName: ${productName} 37 | createDesktopShortcut: always 38 | allowElevation: true 39 | # 是否允许用户更改安装目录 40 | allowToChangeInstallationDirectory: true 41 | portable: 42 | artifactName: ${name}-${version}-portable.${ext} 43 | mac: 44 | executableName: ez-cursor-free 45 | icon: build/icons/512x512.png 46 | entitlementsInherit: build/entitlements.mac.plist 47 | extendInfo: 48 | - NSCameraUsageDescription: Application requests access to the device's camera. 49 | - NSMicrophoneUsageDescription: Application requests access to the device's microphone. 50 | - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder. 51 | - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder. 52 | notarize: false 53 | darkModeSupport: true 54 | # 添加 Intel 和 ARM 支持 55 | target: 56 | - target: dmg 57 | arch: 58 | - x64 59 | - arm64 60 | - target: zip 61 | arch: 62 | - x64 63 | - arm64 64 | # 启用通用二进制支持 65 | artifactName: "${productName}-${version}-${arch}.${ext}" 66 | # 添加 Intel 支持 67 | x64ArchFiles: "**/*" 68 | dmg: 69 | artifactName: ${name}-${version}-${arch}.${ext} 70 | # 添加通用二进制支持 71 | writeUpdateInfo: false 72 | sign: false 73 | linux: 74 | executableName: ez2cursorfree 75 | icon: build/icons/512x512.png 76 | target: 77 | - pacman 78 | - AppImage 79 | - deb 80 | - rpm 81 | - tar.gz 82 | category: Utility 83 | maintainer: galacticdevops.github.io 84 | appImage: 85 | artifactName: ${name}-${version}.${ext} 86 | npmRebuild: false 87 | publish: 88 | provider: github 89 | owner: "GalacticDevOps" 90 | repo: "ez-cursor-free" -------------------------------------------------------------------------------- /electron.vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig, externalizeDepsPlugin } from 'electron-vite' 3 | import vue from '@vitejs/plugin-vue' 4 | 5 | export default defineConfig({ 6 | main: { 7 | plugins: [externalizeDepsPlugin()] 8 | }, 9 | preload: { 10 | plugins: [externalizeDepsPlugin()] 11 | }, 12 | renderer: { 13 | resolve: { 14 | alias: { 15 | '@renderer': resolve('src/renderer/src') 16 | } 17 | }, 18 | plugins: [vue()] 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ez-cursor-free", 3 | "version": "1.1.0", 4 | "description": "Ez2 Use Cursor Free Tools", 5 | "main": "./out/main/index.js", 6 | "author": "Soleil", 7 | "homepage": "https://galacticdevops.github.io", 8 | "scripts": { 9 | "format": "prettier --write .", 10 | "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts,.vue --fix", 11 | "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false", 12 | "typecheck:web": "vue-tsc --noEmit -p tsconfig.web.json --composite false", 13 | "typecheck": "npm run typecheck:node && npm run typecheck:web", 14 | "start": "electron-vite preview", 15 | "dev": "electron-vite dev", 16 | "build": "npm run typecheck && electron-vite build", 17 | "postinstall": "electron-builder install-app-deps", 18 | "build:unpack": "npm run build && electron-builder --dir", 19 | "build:win": "npm run build && electron-builder --win", 20 | "build:mac": "npm run build && electron-builder --mac --universal", 21 | "build:linux": "npm run build && electron-builder --linux", 22 | "build:mac-intel": "npm run build && electron-builder --mac --x64", 23 | "build:mac-arm": "npm run build && electron-builder --mac --arm64", 24 | "electron:generate-icons": "electron-icon-builder --input=./resources/icon.png --output=build --flatten" 25 | }, 26 | "dependencies": { 27 | "@electron-toolkit/preload": "^3.0.0", 28 | "@electron-toolkit/utils": "^3.0.0", 29 | "electron-updater": "^6.1.7" 30 | }, 31 | "devDependencies": { 32 | "@electron-toolkit/eslint-config": "^1.0.2", 33 | "@electron-toolkit/eslint-config-ts": "^2.0.0", 34 | "@electron-toolkit/tsconfig": "^1.0.1", 35 | "@rushstack/eslint-patch": "^1.10.3", 36 | "@types/node": "^20.14.8", 37 | "@vitejs/plugin-vue": "^5.0.5", 38 | "@vue/eslint-config-prettier": "^9.0.0", 39 | "@vue/eslint-config-typescript": "^13.0.0", 40 | "electron": "^31.0.2", 41 | "electron-builder": "^24.13.3", 42 | "electron-vite": "^2.3.0", 43 | "eslint": "^8.57.0", 44 | "eslint-plugin-vue": "^9.26.0", 45 | "prettier": "^3.3.2", 46 | "typescript": "^5.5.2", 47 | "vite": "^5.3.1", 48 | "vue": "^3.4.30", 49 | "vue-tsc": "^2.0.22" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /resources/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/resources/icon.png -------------------------------------------------------------------------------- /resources/workspace/_machine_ids_reset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import uuid 5 | import hashlib 6 | from logger import logging 7 | 8 | 9 | class ResetterMachineIDs: 10 | def __init__(self): 11 | if sys.platform == "win32": 12 | appdata = os.getenv("APPDATA") 13 | if appdata is None: 14 | raise EnvironmentError("not.set.environment.variable.APPDATA") 15 | self.db_path = os.path.join( 16 | appdata, "Cursor", "User", "globalStorage", "storage.json" 17 | ) 18 | elif sys.platform == "darwin": 19 | self.db_path = os.path.abspath( 20 | os.path.expanduser( 21 | "~/Library/Application Support/Cursor/User/globalStorage/storage.json" 22 | ) 23 | ) 24 | elif sys.platform == "linux": 25 | self.db_path = os.path.abspath( 26 | os.path.expanduser("~/.config/Cursor/User/globalStorage/storage.json") 27 | ) 28 | else: 29 | raise NotImplementedError('os.not.supported: {sys.platform}') 30 | 31 | def generate_new_ids(self): 32 | dev_device_id = str(uuid.uuid4()) 33 | machine_id = hashlib.sha256(os.urandom(32)).hexdigest() 34 | mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest() 35 | sqm_id = "{" + str(uuid.uuid4()).upper() + "}" 36 | 37 | return { 38 | "telemetry.devDeviceId": dev_device_id, 39 | "telemetry.macMachineId": mac_machine_id, 40 | "telemetry.machineId": machine_id, 41 | "telemetry.sqmId": sqm_id, 42 | } 43 | 44 | def reset_machine_ids(self): 45 | try: 46 | logging.info('resetting.machine.ids') 47 | 48 | if not os.path.exists(self.db_path): 49 | logging.error('config.not.exists') 50 | return False 51 | 52 | if not os.access(self.db_path, os.R_OK | os.W_OK): 53 | logging.error('config.not.accessible') 54 | logging.warning('read.write.permission.required') 55 | return False 56 | 57 | logging.info('reading.config') 58 | with open(self.db_path, "r", encoding="utf-8") as f: 59 | config = json.load(f) 60 | 61 | logging.info('generating.new.ids') 62 | new_ids = self.generate_new_ids() 63 | 64 | config.update(new_ids) 65 | 66 | logging.info('saving.new.config') 67 | with open(self.db_path, "w", encoding="utf-8") as f: 68 | json.dump(config, f, indent=4) 69 | 70 | logging.info('machine.ids.reset.success') 71 | logging.info('machine.ids.reset.info:') 72 | for key, value in new_ids.items(): 73 | logging.info('key: %s, value: %s' % (key, value)) 74 | 75 | return True 76 | 77 | except PermissionError as e: 78 | logging.error('admin.permission.required') 79 | return False 80 | except Exception as e: 81 | logging.error(f'error.resetting.machine.ids: {str(e)}') 82 | 83 | return False 84 | 85 | 86 | if __name__ == "__main__": 87 | resetter = ResetterMachineIDs() 88 | resetter.reset_machine_ids() -------------------------------------------------------------------------------- /resources/workspace/browser_utils.py: -------------------------------------------------------------------------------- 1 | from DrissionPage import ChromiumOptions, Chromium 2 | import sys 3 | import os 4 | from logger import logging 5 | 6 | 7 | class BrowserManager: 8 | def __init__(self, extension_path=None): 9 | self.browser = None 10 | self.extension_path = extension_path 11 | 12 | def init_browser(self): 13 | co = self._get_browser_options() 14 | self.browser = Chromium(co) 15 | return self.browser 16 | 17 | def _get_browser_options(self): 18 | co = ChromiumOptions() 19 | browser_path = os.getenv("BROWSER_PATH", None) 20 | if browser_path and os.path.exists(browser_path): 21 | co.set_paths(browser_path=browser_path) 22 | try: 23 | extension_path = self._get_extension_path() 24 | if extension_path: 25 | co.add_extension(extension_path) 26 | logging.info('extension.loaded') 27 | else: 28 | logging.warning('extension.not.loaded') 29 | except Exception as e: 30 | logging.warning(f'extension.load.error {str(e)}') 31 | 32 | co.set_user_agent( 33 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36" 34 | ) 35 | co.set_pref("credentials_enable_service", False) 36 | co.set_argument("--hide-crash-restore-bubble") 37 | proxy = os.getenv('BROWSER_PROXY') 38 | if proxy: 39 | co.set_proxy(proxy) 40 | co.auto_port() 41 | co.headless(os.getenv('BROWSER_HEADLESS', 'True').lower() == 'true') 42 | 43 | if sys.platform == "darwin": 44 | co.set_argument("--no-sandbox") 45 | co.set_argument("--disable-gpu") 46 | 47 | return co 48 | 49 | def _get_extension_path(self): 50 | if self.extension_path and os.path.exists(self.extension_path): 51 | return self.extension_path 52 | 53 | script_dir = os.path.dirname(os.path.abspath(__file__)) 54 | extension_path = os.path.join(script_dir, "turnstilePatch") 55 | 56 | if hasattr(sys, "_MEIPASS"): 57 | extension_path = os.path.join(sys._MEIPASS, "turnstilePatch") 58 | 59 | if os.path.exists(extension_path): 60 | required_files = ['manifest.json', 'script.js'] 61 | if all(os.path.exists(os.path.join(extension_path, f)) for f in required_files): 62 | return extension_path 63 | else: 64 | logging.warning(f'not.all.required.files {required_files}') 65 | else: 66 | raise FileNotFoundError(f'extension.not.found {extension_path}') 67 | 68 | return None 69 | 70 | def quit(self): 71 | if self.browser: 72 | try: 73 | self.browser.quit() 74 | except: 75 | pass 76 | -------------------------------------------------------------------------------- /resources/workspace/cursor_auth_manager.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | import sys 4 | from logger import logging 5 | 6 | 7 | class CursorAuthManager: 8 | def __init__(self): 9 | if os.name == "nt": # Windows 10 | appdata = os.getenv("APPDATA") 11 | if appdata is None: 12 | raise EnvironmentError("not.set.environment.variable.APPDATA") 13 | self.db_path = os.path.join( 14 | appdata, "Cursor", "User", "globalStorage", "state.vscdb" 15 | ) 16 | elif os.name == 'posix': # Linux or macOS 17 | if os.uname().sysname == 'Darwin': 18 | self.db_path = os.path.expanduser( 19 | "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" 20 | ) 21 | else: # Linux 22 | self.db_path = os.path.expanduser( 23 | "~/.config/Cursor/User/globalStorage/state.vscdb" 24 | ) 25 | else: 26 | raise NotImplementedError('os.not.supported: {sys.platform}') 27 | 28 | logging.info(f"auth.db.path: {self.db_path}") 29 | 30 | 31 | def update_auth(self, email=None, access_token=None, refresh_token=None): 32 | conn = None 33 | try: 34 | db_dir = os.path.dirname(self.db_path) 35 | if not os.path.exists(db_dir): 36 | os.makedirs(db_dir, mode=0o755, exist_ok=True) 37 | 38 | if not os.path.exists(self.db_path): 39 | conn = sqlite3.connect(self.db_path) 40 | cursor = conn.cursor() 41 | cursor.execute(''' 42 | CREATE TABLE IF NOT EXISTS ItemTable ( 43 | key TEXT PRIMARY KEY, 44 | value TEXT 45 | ) 46 | ''') 47 | conn.commit() 48 | if sys.platform != "win32": 49 | os.chmod(self.db_path, 0o644) 50 | conn.close() 51 | 52 | conn = sqlite3.connect(self.db_path) 53 | logging.info('auth.connected.database') 54 | cursor = conn.cursor() 55 | 56 | conn.execute("PRAGMA busy_timeout = 5000") 57 | conn.execute("PRAGMA journal_mode = WAL") 58 | conn.execute("PRAGMA synchronous = NORMAL") 59 | 60 | updates = [] 61 | if email is not None: 62 | updates.append(("cursorAuth/cachedEmail", email)) 63 | if access_token is not None: 64 | updates.append(("cursorAuth/accessToken", access_token)) 65 | if refresh_token is not None: 66 | updates.append(("cursorAuth/refreshToken", refresh_token)) 67 | updates.append(("cursorAuth/cachedSignUpType", "Auth_0")) 68 | 69 | cursor.execute("BEGIN TRANSACTION") 70 | try: 71 | for key, value in updates: 72 | cursor.execute("SELECT COUNT(*) FROM ItemTable WHERE key = ?", (key,)) 73 | if cursor.fetchone()[0] == 0: 74 | cursor.execute(""" 75 | INSERT INTO ItemTable (key, value) 76 | VALUES (?, ?) 77 | """, (key, value)) 78 | else: 79 | cursor.execute(""" 80 | UPDATE ItemTable SET value = ? 81 | WHERE key = ? 82 | """, (value, key)) 83 | logging.info(f'auth.updating {key.split('/')[-1]}') 84 | 85 | cursor.execute("COMMIT") 86 | logging.info('auth.database.updated.successfully') 87 | return True 88 | 89 | except Exception as e: 90 | cursor.execute("ROLLBACK") 91 | raise e 92 | 93 | except sqlite3.Error as e: 94 | logging.error("database.error:", str(e)) 95 | return False 96 | except Exception as e: 97 | logging.error("error:", str(e)) 98 | return False 99 | finally: 100 | if conn: 101 | conn.close() 102 | -------------------------------------------------------------------------------- /resources/workspace/cursor_pro_keep_alive.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import psutil 4 | import subprocess 5 | 6 | from exit_cursor import ExitCursor 7 | 8 | os.environ["PYTHONWARNINGS"] = "ignore" 9 | 10 | import time 11 | import random 12 | 13 | import os 14 | from logger import logging 15 | from cursor_auth_manager import CursorAuthManager 16 | from browser_utils import BrowserManager 17 | from get_email_code import EmailVerificationHandler 18 | from _machine_ids_reset import ResetterMachineIDs 19 | 20 | if sys.stdout.encoding != 'utf-8': 21 | sys.stdout.reconfigure(encoding='utf-8') 22 | if sys.stderr.encoding != 'utf-8': 23 | sys.stderr.reconfigure(encoding='utf-8') 24 | 25 | LOGIN_URL = "https://authenticator.cursor.sh" 26 | SIGN_UP_URL = "https://authenticator.cursor.sh/sign-up" 27 | SETTINGS_URL = "https://www.cursor.com/settings" 28 | MAIL_URL = "https://mail.cx/zh/" 29 | TOTAL_USAGE = 0 30 | 31 | def handle_turnstile(tab): 32 | try: 33 | while True: 34 | try: 35 | challengeCheck = ( 36 | tab.ele("@id=cf-turnstile", timeout=2) 37 | .child() 38 | .shadow_root.ele("tag:iframe") 39 | .ele("tag:body") 40 | .sr("tag:input") 41 | ) 42 | 43 | if challengeCheck: 44 | time.sleep(random.uniform(1, 3)) 45 | challengeCheck.click() 46 | time.sleep(2) 47 | return True 48 | except: 49 | pass 50 | 51 | if tab.ele("@name=password"): 52 | break 53 | if tab.ele("@data-index=0"): 54 | break 55 | if tab.ele("Account Settings"): 56 | break 57 | 58 | time.sleep(random.uniform(1, 2)) 59 | except Exception as e: 60 | logging.error(e) 61 | return False 62 | 63 | 64 | def get_cursor_session_token(tab, max_attempts=5, retry_interval=3): 65 | try: 66 | 67 | tab.get(SETTINGS_URL) 68 | time.sleep(5) 69 | 70 | usage_selector = ( 71 | "css:div.col-span-2 > div > div > div > div > " 72 | "div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > " 73 | "span.font-mono.text-sm\\/\\[0\\.875rem\\]" 74 | ) 75 | usage_ele = tab.ele(usage_selector) 76 | total_usage = "null" 77 | if usage_ele: 78 | total_usage = usage_ele.text.split("/")[-1].strip() 79 | global TOTAL_USAGE 80 | TOTAL_USAGE = total_usage 81 | logging.info(f"total_usage: {total_usage}") 82 | 83 | 84 | logging.info("get.cookie") 85 | attempts = 0 86 | 87 | while attempts < max_attempts: 88 | try: 89 | cookies = tab.cookies() 90 | for cookie in cookies: 91 | if cookie.get("name") == "WorkosCursorSessionToken": 92 | return cookie["value"].split("%3A%3A")[1] 93 | 94 | attempts += 1 95 | if attempts < max_attempts: 96 | logging.warning(f"not.find.cursor_session_token, retrying in {retry_interval} seconds...") 97 | time.sleep(retry_interval) 98 | else: 99 | logging.error(f"not.find.cursor_session_token") 100 | 101 | except Exception as e: 102 | logging.info(f"get_cursor_session_token.error: {str(e)}") 103 | attempts += 1 104 | if attempts < max_attempts: 105 | logging.info(f"get_cursor_session_token.error, retrying in {retry_interval} seconds...") 106 | time.sleep(retry_interval) 107 | 108 | return False 109 | 110 | except Exception as e: 111 | logging.warning(f"get_cursor_session_token.error: {str(e)}") 112 | return False 113 | 114 | def update_cursor_auth(email=None, access_token=None, refresh_token=None): 115 | auth_manager = CursorAuthManager() 116 | return auth_manager.update_auth(email, access_token, refresh_token) 117 | 118 | 119 | def get_temp_email(tab): 120 | max_retries = 15 121 | last_email = None 122 | stable_count = 0 123 | 124 | logging.info('wait.email') 125 | for i in range(max_retries): 126 | try: 127 | email_input = tab.ele("css:input.bg-gray-200[disabled]", timeout=3) 128 | if email_input: 129 | current_email = email_input.attr('value') 130 | if current_email and '@' in current_email: 131 | if current_email == last_email: 132 | stable_count += 1 133 | if stable_count >= 2: 134 | logging.info('email.success') 135 | return current_email 136 | else: 137 | stable_count = 0 138 | last_email = current_email 139 | logging.info(f'current_email: {current_email}') 140 | 141 | logging.info('wait.email') 142 | time.sleep(2) 143 | 144 | except Exception as e: 145 | logging.warning('get_temp_email.error') 146 | time.sleep(2) 147 | stable_count = 0 148 | 149 | raise ValueError('not.find.email') 150 | 151 | 152 | def sign_up_account(browser, tab, account_info): 153 | logging.info('sign_up_account') 154 | tab.get(SIGN_UP_URL) 155 | 156 | try: 157 | if tab.ele("@name=first_name"): 158 | tab.actions.click("@name=first_name").input(account_info["first_name"]) 159 | time.sleep(random.uniform(1, 3)) 160 | 161 | tab.actions.click("@name=last_name").input(account_info["last_name"]) 162 | time.sleep(random.uniform(1, 3)) 163 | 164 | tab.actions.click("@name=email").input(account_info["email"]) 165 | time.sleep(random.uniform(1, 3)) 166 | 167 | tab.actions.click("@type=submit") 168 | 169 | except Exception as e: 170 | logging.warning('name.error') 171 | return False 172 | 173 | handle_turnstile(tab) 174 | 175 | try: 176 | if tab.ele("@name=password"): 177 | tab.ele("@name=password").input(account_info["password"]) 178 | time.sleep(random.uniform(1, 3)) 179 | 180 | tab.ele("@type=submit").click() 181 | logging.info('password.success') 182 | 183 | except Exception as e: 184 | logging.warning('password.error') 185 | return False 186 | 187 | time.sleep(random.uniform(1, 3)) 188 | if tab.ele("This email is not available."): 189 | logging.warning('email.not_available') 190 | return False 191 | 192 | handle_turnstile(tab) 193 | 194 | email_handler = EmailVerificationHandler(browser, MAIL_URL) 195 | 196 | while True: 197 | try: 198 | if tab.ele("Account Settings"): 199 | break 200 | if tab.ele("@data-index=0"): 201 | code = email_handler.get_verification_code(account_info["email"]) 202 | if not code: 203 | return False 204 | 205 | i = 0 206 | for digit in code: 207 | tab.ele(f"@data-index={i}").input(digit) 208 | time.sleep(random.uniform(0.1, 0.3)) 209 | i += 1 210 | break 211 | except Exception as e: 212 | logging.error(e) 213 | 214 | handle_turnstile(tab) 215 | return True 216 | 217 | def handle_verification_code(browser, tab, account_info): 218 | email_handler = EmailVerificationHandler(browser, MAIL_URL) 219 | 220 | max_wait = 30 221 | start_time = time.time() 222 | 223 | while time.time() - start_time < max_wait: 224 | try: 225 | if tab.ele("Account Settings"): 226 | logging.info('email.success') 227 | return True 228 | 229 | if tab.ele("@data-index=0"): 230 | code = email_handler.get_verification_code(account_info["email"]) 231 | if not code: 232 | logging.error("无法获取验证码") 233 | return False 234 | 235 | logging.info(f'code: {code}') 236 | for i, digit in enumerate(code): 237 | tab.ele(f"@data-index={i}").input(digit) 238 | time.sleep(random.uniform(0.1, 0.3)) 239 | return True 240 | 241 | time.sleep(2) 242 | 243 | except Exception as e: 244 | logging.error(f"handle_verification_code.error: {str(e)}") 245 | time.sleep(2) 246 | 247 | logging.error('email.timeout') 248 | return False 249 | 250 | 251 | class EmailGenerator: 252 | FIRST_NAMES = [ 253 | "james", "john", "robert", "michael", "william", "david", "richard", "joseph", 254 | "thomas", "charles", "christopher", "daniel", "matthew", "anthony", "donald", 255 | "emma", "olivia", "ava", "isabella", "sophia", "mia", "charlotte", "amelia", 256 | "harper", "evelyn", "abigail", "emily", "elizabeth", "sofia", "madison" 257 | ] 258 | 259 | LAST_NAMES = [ 260 | "smith", "johnson", "williams", "brown", "jones", "garcia", "miller", "davis", 261 | "rodriguez", "martinez", "hernandez", "lopez", "gonzalez", "wilson", "anderson", 262 | "thomas", "taylor", "moore", "jackson", "martin", "lee", "perez", "thompson", 263 | "white", "harris", "sanchez", "clark", "ramirez", "lewis", "robinson" 264 | ] 265 | 266 | def __init__( 267 | self, 268 | password="".join( 269 | random.choices( 270 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*", 271 | k=12, 272 | ) 273 | ), 274 | first_name=None, 275 | last_name=None, 276 | ): 277 | self.default_password = password 278 | self.default_first_name = first_name or random.choice(self.FIRST_NAMES) 279 | self.default_last_name = last_name or random.choice(self.LAST_NAMES) 280 | self.email = None 281 | 282 | def set_email(self, email): 283 | self.email = email 284 | 285 | def get_account_info(self): 286 | if not self.email: 287 | raise ValueError("Email address not set") 288 | return { 289 | "email": self.email, 290 | "password": self.default_password, 291 | "first_name": self.default_first_name.capitalize(), 292 | "last_name": self.default_last_name.capitalize(), 293 | } 294 | 295 | def _save_account_info(self, token, total_usage): 296 | try: 297 | file_path = os.path.join(os.getcwd(), 'cursor_accounts.txt') 298 | with open(file_path, 'a', encoding='utf-8') as f: 299 | f.write(f"\n{'='*50}\n") 300 | f.write(f"Email: {self.email}\n") 301 | f.write(f"Password: {self.default_password}\n") 302 | f.write(f"Token: {token}\n") 303 | f.write(f"Usage Limit: {total_usage}\n") 304 | f.write(f"{'='*50}\n") 305 | return True 306 | except Exception as e: 307 | logging.error(f"save_account_info.error: {str(e)}") 308 | return False 309 | 310 | def cleanup_and_exit(browser_manager=None, exit_code=0): 311 | """Clean up resources and exit program""" 312 | try: 313 | if browser_manager: 314 | logging.info("browser.quit") 315 | browser_manager.quit() 316 | 317 | current_process = psutil.Process() 318 | children = current_process.children(recursive=True) 319 | for child in children: 320 | try: 321 | child.terminate() 322 | except: 323 | pass 324 | 325 | logging.info("exit.success") 326 | sys.exit(exit_code) 327 | 328 | except Exception as e: 329 | logging.error(f"cleanup.exit.error: {str(e)}") 330 | sys.exit(1) 331 | 332 | 333 | 334 | def main(): 335 | browser_manager = None 336 | try: 337 | success, path_cursor = ExitCursor() # 在调试时可以注释掉 338 | logging.info(f'exit.cursor.success: {success}, path.cursor: {path_cursor}') 339 | 340 | browser_manager = BrowserManager() 341 | browser = browser_manager.init_browser() 342 | 343 | mail_tab = browser.new_tab(MAIL_URL) 344 | browser.activate_tab(mail_tab) 345 | time.sleep(5) 346 | 347 | email_js = get_temp_email(mail_tab) 348 | 349 | email_generator = EmailGenerator() 350 | email_generator.set_email(email_js) 351 | account_info = email_generator.get_account_info() 352 | 353 | signup_tab = browser.new_tab(SIGN_UP_URL) 354 | browser.activate_tab(signup_tab) 355 | time.sleep(2) 356 | 357 | signup_tab.run_js("try { turnstile.reset() } catch(e) { }") 358 | 359 | if sign_up_account(browser, signup_tab, account_info): 360 | token = get_cursor_session_token(signup_tab) 361 | logging.info(f'account.token: {token}') 362 | if token: 363 | email_generator._save_account_info(token, TOTAL_USAGE) 364 | update_cursor_auth( 365 | email=account_info["email"], access_token=token, refresh_token=token 366 | ) 367 | logging.info('start.machine.ids.reset') 368 | resetter = ResetterMachineIDs() 369 | resetter.reset_machine_ids() 370 | if path_cursor: 371 | try: 372 | logging.info(f"restart.cursor.path {path_cursor}") 373 | if os.name == 'nt': 374 | startupinfo = subprocess.STARTUPINFO() 375 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 376 | subprocess.Popen([path_cursor], startupinfo=startupinfo, close_fds=True) 377 | else: 378 | subprocess.Popen(['open', path_cursor]) 379 | logging.info('restart.cursor.success') 380 | except Exception as e: 381 | logging.error(f"restart.cursor.error: {str(e)}") 382 | else: 383 | logging.error("get.cursor.session.token.failed") 384 | else: 385 | logging.error("register.failed") 386 | 387 | logging.info("register.finished") 388 | cleanup_and_exit(browser_manager, 0) 389 | 390 | except Exception as e: 391 | logging.error(f"main.error: {str(e)}") 392 | import traceback 393 | logging.error(traceback.format_exc()) 394 | cleanup_and_exit(browser_manager, 1) 395 | finally: 396 | cleanup_and_exit(browser_manager, 1) 397 | 398 | 399 | if __name__ == "__main__": 400 | main() 401 | -------------------------------------------------------------------------------- /resources/workspace/exit_cursor.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | from logger import logging 3 | import time 4 | 5 | def ExitCursor(timeout=5): 6 | try: 7 | logging.info("exit.cursor") 8 | cursor_processes = [] 9 | path_cursor = '' 10 | for proc in psutil.process_iter(['pid', 'name']): 11 | try: 12 | if proc.info['name'].lower() in ['cursor.exe', 'cursor']: 13 | try: 14 | if not path_cursor: 15 | raw_path = proc.exe() 16 | if raw_path and '.app' in raw_path: 17 | path_cursor = raw_path[:raw_path.find('.app') + 4] 18 | else: 19 | path_cursor = raw_path 20 | logging.info(f'found.cursor {path_cursor}') 21 | except (psutil.NoSuchProcess, psutil.AccessDenied): 22 | logging.warning('exit.cursor.path.error') 23 | cursor_processes.append(proc) 24 | except (psutil.NoSuchProcess, psutil.AccessDenied): 25 | continue 26 | 27 | if not cursor_processes: 28 | logging.info("not.found.cursor") 29 | return True, path_cursor 30 | 31 | for proc in cursor_processes: 32 | try: 33 | if proc.is_running(): 34 | proc.terminate() 35 | except (psutil.NoSuchProcess, psutil.AccessDenied): 36 | continue 37 | 38 | start_time = time.time() 39 | while time.time() - start_time < timeout: 40 | still_running = [] 41 | for proc in cursor_processes: 42 | try: 43 | if proc.is_running(): 44 | still_running.append(proc) 45 | except (psutil.NoSuchProcess, psutil.AccessDenied): 46 | continue 47 | 48 | if not still_running: 49 | logging.info("exit.cursor.success") 50 | return True, path_cursor 51 | 52 | time.sleep(0.5) 53 | 54 | if still_running: 55 | process_list = ", ".join([str(p.pid) for p in still_running]) 56 | logging.warning(f'exit.cursor.timeout {process_list}') 57 | return False, path_cursor 58 | 59 | return True, path_cursor 60 | 61 | except Exception as e: 62 | logging.error(f'exit.cursor.error {str(e)}') 63 | return False, '' 64 | 65 | if __name__ == "__main__": 66 | success, path = ExitCursor() 67 | if path: 68 | logging.info(f'exit.cursor.path {path}') 69 | -------------------------------------------------------------------------------- /resources/workspace/get_email_code.py: -------------------------------------------------------------------------------- 1 | from DrissionPage.common import Keys 2 | import time 3 | import re 4 | from logger import logging 5 | 6 | 7 | class EmailVerificationHandler: 8 | def __init__(self, browser, mail_url): 9 | self.browser = browser 10 | self.mail_url = mail_url 11 | 12 | def get_verification_code(self, email): 13 | logging.info(email) 14 | code = None 15 | 16 | try: 17 | logging.info("processing.email") 18 | tab_mail = self.browser.new_tab(self.mail_url) 19 | self.browser.activate_tab(tab_mail) 20 | 21 | code = self._get_latest_mail_code(tab_mail) 22 | 23 | # self._cleanup_mail(tab_mail) 24 | 25 | tab_mail.close() 26 | 27 | except Exception as e: 28 | logging.error(f"error.getting.email.code: {str(e)}") 29 | 30 | return code 31 | 32 | def _get_latest_mail_code(self, tab): 33 | code = None 34 | retry_count = 0 35 | max_retries = 3 36 | 37 | while retry_count < max_retries: 38 | try: 39 | email_row = tab.ele("css:tbody > tr.border-b.cursor-pointer", timeout=2) 40 | if email_row: 41 | subject_cell = email_row.ele("css:td:nth-child(2)") 42 | if subject_cell and "Verify your email address" in subject_cell.text: 43 | logging.info('email.found') 44 | email_row.click() 45 | time.sleep(2) 46 | break 47 | 48 | logging.info("waiting.email.load") 49 | time.sleep(2) 50 | tab.refresh() 51 | time.sleep(3) 52 | retry_count += 1 53 | 54 | except Exception as e: 55 | logging.error(f"error.getting.email: {str(e)}") 56 | time.sleep(2) 57 | retry_count += 1 58 | 59 | if retry_count >= max_retries: 60 | logging.error("email.not.found") 61 | raise Exception("Email not found") 62 | 63 | max_retries = 10 64 | for attempt in range(max_retries): 65 | try: 66 | content_td = tab.ele("css:td.px-3.text-black.text-base", timeout=2) 67 | if content_td: 68 | content = content_td.text 69 | if content: 70 | matches = re.findall(r'\b\d{6}\b', content) 71 | for match in matches: 72 | if "verification code" in content.lower() or "verify" in content.lower(): 73 | logging.info(f"code.found: {match}") 74 | return match 75 | 76 | logging.info("waiting.code.load") 77 | time.sleep(2) 78 | 79 | except Exception as e: 80 | logging.error(f"error.getting.code: {str(e)}") 81 | time.sleep(2) 82 | 83 | logging.error("code.not.found") 84 | return None 85 | 86 | def _cleanup_mail(self, tab): 87 | if tab.ele("@id=delete_mail"): 88 | tab.actions.click("@id=delete_mail") 89 | time.sleep(1) 90 | 91 | if tab.ele("@id=confirm_mail"): 92 | tab.actions.click("@id=confirm_mail") 93 | -------------------------------------------------------------------------------- /resources/workspace/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from datetime import datetime 4 | 5 | # Configure logging 6 | log_dir = "logs" 7 | if not os.path.exists(log_dir): 8 | os.makedirs(log_dir) 9 | 10 | logging.basicConfig( 11 | filename=os.path.join(log_dir, f"{datetime.now().strftime('%Y-%m-%d')}.log"), 12 | level=logging.DEBUG, 13 | format="%(asctime)s - %(levelname)s - %(message)s", 14 | encoding='utf-8', 15 | ) 16 | 17 | # 创建控制台处理器 18 | console_handler = logging.StreamHandler() 19 | console_handler.setLevel(logging.INFO) 20 | console_handler.setFormatter(logging.Formatter("%(message)s")) 21 | 22 | # 将控制台处理器添加到日志记录器 23 | logging.getLogger().addHandler(console_handler) 24 | 25 | def main_task(): 26 | """ 27 | Main task execution function. Simulates a workflow and handles errors. 28 | """ 29 | try: 30 | logging.info("Starting the main task...") 31 | 32 | # Simulated task and error condition 33 | if some_condition(): 34 | raise ValueError("Simulated error occurred.") 35 | 36 | logging.info("Main task completed successfully.") 37 | 38 | except ValueError as ve: 39 | logging.error(f"ValueError occurred: {ve}", exc_info=True) 40 | except Exception as e: 41 | logging.error(f"Unexpected error occurred: {e}", exc_info=True) 42 | finally: 43 | logging.info("Task execution finished.") 44 | 45 | def some_condition(): 46 | """ 47 | Simulates an error condition. Returns True to trigger an error. 48 | Replace this logic with actual task conditions. 49 | """ 50 | return True 51 | 52 | if __name__ == "__main__": 53 | # Application workflow 54 | logging.info("Application started.") 55 | main_task() 56 | logging.info("Application exited.") 57 | -------------------------------------------------------------------------------- /resources/workspace/requirements.txt: -------------------------------------------------------------------------------- 1 | DrissionPage==4.1.0.9 2 | psutil==6.1.0 -------------------------------------------------------------------------------- /resources/workspace/turnstilePatch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Turnstile Patcher", 4 | "version": "2.1", 5 | "content_scripts": [ 6 | { 7 | "js": [ 8 | "./script.js" 9 | ], 10 | "matches": [ 11 | "" 12 | ], 13 | "run_at": "document_start", 14 | "all_frames": true, 15 | "world": "MAIN" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /resources/workspace/turnstilePatch/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/workspace/turnstilePatch/script.js: -------------------------------------------------------------------------------- 1 | function getRandomInt(min, max) { 2 | return Math.floor(Math.random() * (max - min + 1)) + min; 3 | } 4 | 5 | // old method wouldn't work on 4k screens 6 | 7 | let screenX = getRandomInt(800, 1200); 8 | let screenY = getRandomInt(400, 600); 9 | 10 | Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX }); 11 | 12 | Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY }); -------------------------------------------------------------------------------- /screenshots/alert.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/screenshots/alert.png -------------------------------------------------------------------------------- /screenshots/auto_reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/screenshots/auto_reset.png -------------------------------------------------------------------------------- /screenshots/disable_update.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/screenshots/disable_update.png -------------------------------------------------------------------------------- /screenshots/ez2cursor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GalacticDevOps/ez-cursor-free/29a561b4760592a092c112915bddf3345d34fbe0/screenshots/ez2cursor.png -------------------------------------------------------------------------------- /src/main/index.ts: -------------------------------------------------------------------------------- 1 | import { app, shell, BrowserWindow, ipcMain, net } from 'electron' 2 | import { join, dirname } from 'path' 3 | import { electronApp, optimizer, is } from '@electron-toolkit/utils' 4 | import icon from '../../resources/icon.png?asset' 5 | import { homedir } from 'os' 6 | import { existsSync, readFileSync, writeFileSync, chmodSync, statSync, copyFileSync, mkdirSync as mkdir } from 'fs' 7 | import { platform } from 'os' 8 | import { StorageData, ModifyResult, CurrentIds } from './types' 9 | import { compare } from 'semver' 10 | import { spawn, spawnSync } from 'child_process' 11 | import path from 'path' 12 | import { exec } from 'child_process' 13 | 14 | let mainWindow: BrowserWindow | null = null 15 | 16 | function getStoragePath(): string { 17 | const home = homedir() 18 | 19 | switch (platform()) { 20 | case 'win32': 21 | return join(home, 'AppData', 'Roaming', 'Cursor', 'User', 'globalStorage', 'storage.json') 22 | case 'darwin': 23 | return join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'storage.json') 24 | case 'linux': 25 | return join(home, '.config', 'Cursor', 'User', 'globalStorage', 'storage.json') 26 | default: 27 | throw new Error('不支持的操作系统') 28 | } 29 | } 30 | 31 | function generateUUID(): string { 32 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 33 | const r = Math.random() * 16 | 0 34 | const v = c == 'x' ? r : (r & 0x3 | 0x8) 35 | return v.toString(16) 36 | }) 37 | } 38 | 39 | function generateMachineId(): string { 40 | // 生成32字节的十六进制字符串 41 | const bytes = new Array(32).fill(0).map(() => Math.floor(Math.random() * 256)) 42 | return bytes.map(b => b.toString(16).padStart(2, '0')).join('') 43 | } 44 | 45 | function generateSqmId(): string { 46 | // 生成带大括号的UUID格式 47 | return `{${generateUUID().toUpperCase()}}` 48 | } 49 | 50 | function createWindow(): void { 51 | // Create the browser window. 52 | mainWindow = new BrowserWindow({ 53 | width: 800, 54 | height: 550, 55 | show: false, 56 | autoHideMenuBar: true, 57 | frame: true, 58 | resizable: false, 59 | maximizable: false, 60 | fullscreenable: false, 61 | center: true, 62 | icon: join(__dirname,'../../resources/icon.png'), 63 | ...(process.platform === 'linux' ? { icon } : {}), 64 | webPreferences: { 65 | preload: join(__dirname, '../preload/index.js'), 66 | sandbox: false 67 | } 68 | }) 69 | 70 | mainWindow.on('ready-to-show', () => { 71 | mainWindow?.show() 72 | }) 73 | 74 | // 设置窗口大小限制 75 | mainWindow.setMinimumSize(800, 550) 76 | 77 | mainWindow.webContents.setWindowOpenHandler((details) => { 78 | shell.openExternal(details.url) 79 | return { action: 'deny' } 80 | }) 81 | 82 | // HMR for renderer base on electron-vite cli. 83 | // Load the remote URL for development or the local html file for production. 84 | if (is.dev && process.env['ELECTRON_RENDERER_URL']) { 85 | mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL']) 86 | } else { 87 | mainWindow.loadFile(join(__dirname, '../renderer/index.html')) 88 | } 89 | 90 | // 添加窗口关闭事件处理 91 | mainWindow.on('closed', () => { 92 | mainWindow = null 93 | }) 94 | } 95 | 96 | // This method will be called when Electron has finished 97 | // initialization and is ready to create browser windows. 98 | // Some APIs can only be used after this event occurs. 99 | app.whenReady().then(() => { 100 | // Set app user model id for windows 101 | electronApp.setAppUserModelId('com.electron') 102 | 103 | // Default open or close DevTools by F12 in development 104 | // and ignore CommandOrControl + R in production. 105 | // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils 106 | app.on('browser-window-created', (_, window) => { 107 | optimizer.watchWindowShortcuts(window) 108 | }) 109 | 110 | // IPC Handlers 111 | ipcMain.handle('get-current-ids', async (): Promise => { 112 | try { 113 | const configPath = getStoragePath() 114 | let data: StorageData = {} 115 | 116 | if (existsSync(configPath)) { 117 | const content = readFileSync(configPath, 'utf8') 118 | data = JSON.parse(content) 119 | } 120 | 121 | return { 122 | configPath, 123 | machineId: data['telemetry.machineId'] || '', 124 | macMachineId: data['telemetry.macMachineId'] || '', 125 | devDeviceId: data['telemetry.devDeviceId'] || '', 126 | sqmId: data['telemetry.sqmId'] || '' 127 | } 128 | } catch (error) { 129 | const err = error as Error 130 | throw new Error(`读取配置失败: ${err.message || '未知错误'}`) 131 | } 132 | }) 133 | 134 | ipcMain.handle('modify-ids', async (): Promise => { 135 | try { 136 | const configPath = getStoragePath() 137 | let data: StorageData = {} 138 | 139 | if (existsSync(configPath)) { 140 | const content = readFileSync(configPath, 'utf8') 141 | data = JSON.parse(content) 142 | } 143 | 144 | // 生成新的 ID 145 | data['telemetry.machineId'] = generateMachineId() 146 | data['telemetry.macMachineId'] = generateUUID() 147 | data['telemetry.devDeviceId'] = generateUUID() 148 | data['telemetry.sqmId'] = generateSqmId() 149 | 150 | // 写入文件 151 | writeFileSync(configPath, JSON.stringify(data, null, 2)) 152 | 153 | return { success: true } 154 | } catch (error) { 155 | const err = error as Error 156 | return { success: false, error: err.message || '未知错误' } 157 | } 158 | }) 159 | 160 | ipcMain.handle('set-file-permission', async (_, isReadOnly: boolean): Promise => { 161 | try { 162 | const configPath = getStoragePath() 163 | chmodSync(configPath, isReadOnly ? 0o444 : 0o666) 164 | return { success: true } 165 | } catch (error) { 166 | const err = error as Error 167 | return { success: false, error: err.message || '未知错误' } 168 | } 169 | }) 170 | 171 | ipcMain.handle('check-file-permission', async (): Promise<{ isReadOnly: boolean }> => { 172 | try { 173 | const configPath = getStoragePath() 174 | const stats = statSync(configPath) 175 | // 检查文件是否为只读模式 176 | const isReadOnly = (stats.mode & 0o222) === 0 177 | return { isReadOnly } 178 | } catch (error) { 179 | const err = error as Error 180 | throw new Error(`检查文件权限失败: ${err.message || '未知错误'}`) 181 | } 182 | }) 183 | 184 | ipcMain.handle('backup-storage', async (): Promise => { 185 | try { 186 | const sourcePath = getStoragePath() 187 | const backupPath = getBackupPath() 188 | 189 | if (!existsSync(sourcePath)) { 190 | return { success: false, error: '配置文件不存在' } 191 | } 192 | 193 | copyFileSync(sourcePath, backupPath) 194 | return { success: true } 195 | } catch (error) { 196 | const err = error as Error 197 | return { success: false, error: err.message || '备份失败' } 198 | } 199 | }) 200 | 201 | ipcMain.handle('restore-storage', async (): Promise => { 202 | try { 203 | const sourcePath = getBackupPath() 204 | const targetPath = getStoragePath() 205 | 206 | if (!existsSync(sourcePath)) { 207 | return { success: false, error: '备份文件不存在' } 208 | } 209 | 210 | // 验证备份文件格式 211 | try { 212 | const content = readFileSync(sourcePath, 'utf8') 213 | JSON.parse(content) // 验证 JSON 格式 214 | } catch { 215 | return { success: false, error: '备份文件格式错误' } 216 | } 217 | 218 | copyFileSync(sourcePath, targetPath) 219 | return { success: true } 220 | } catch (error) { 221 | const err = error as Error 222 | return { success: false, error: err.message || '还原失败' } 223 | } 224 | }) 225 | 226 | ipcMain.handle('check-backup-exists', async (): Promise<{ exists: boolean }> => { 227 | const backupPath = getBackupPath() 228 | return { exists: existsSync(backupPath) } 229 | }) 230 | 231 | ipcMain.handle('check-updates', async () => { 232 | return await checkForUpdates() 233 | }) 234 | 235 | ipcMain.handle('open-release-page', async (_, url: string) => { 236 | shell.openExternal(url) 237 | return { success: true } 238 | }) 239 | 240 | // 添加保活处理器 241 | ipcMain.handle('start-keep-alive', async () => { 242 | try { 243 | // 准备插件目录 244 | const extDir = copyExtensionFiles() 245 | if (!extDir) { 246 | return { 247 | success: false, 248 | error: '插件目录准备失败,请检查文件权限' 249 | } 250 | } 251 | 252 | const scriptPath = path.join(__dirname, '../../workspace/cursor_pro_keep_alive.py') 253 | 254 | // 使用 spawn 运行 Python 脚本,添加插件路径参数 255 | const pythonProcess = spawn('python', [ 256 | scriptPath, 257 | '--extension-path', extDir 258 | ], { 259 | stdio: ['pipe', 'pipe', 'pipe'], 260 | env: { 261 | ...process.env, 262 | PYTHONIOENCODING: 'utf-8', 263 | PYTHONUNBUFFERED: '1' 264 | } 265 | }) 266 | 267 | // 处理标准输出 268 | pythonProcess.stdout.on('data', (data) => { 269 | mainWindow?.webContents.send('python-output', data.toString()) 270 | }) 271 | 272 | // 处理标准错误 273 | pythonProcess.stderr.on('data', (data) => { 274 | mainWindow?.webContents.send('python-error', data.toString()) 275 | }) 276 | 277 | return { success: true } 278 | } catch (error) { 279 | return { 280 | success: false, 281 | error: error instanceof Error ? error.message : '运行脚本失败' 282 | } 283 | } 284 | }) 285 | 286 | // 修改 Python 环境检测处理程序 287 | ipcMain.handle('check-python-env', async () => { 288 | return new Promise((resolve) => { 289 | const pythonPath = findPythonPath() 290 | console.log('使用 Python 路径:', pythonPath) 291 | 292 | // 使用 Python 的 --version 命令来检测版本 293 | const pythonProcess = spawn(pythonPath, ['--version'], { 294 | shell: true, 295 | env: { 296 | ...process.env, 297 | PYTHONIOENCODING: 'utf-8', 298 | PYTHONUNBUFFERED: '1' 299 | } 300 | }) 301 | 302 | let output = '' 303 | let error = '' 304 | 305 | pythonProcess.stdout.on('data', (data) => { 306 | output += data.toString() 307 | console.log('Python 标准输出:', data.toString()) 308 | }) 309 | 310 | pythonProcess.stderr.on('data', (data) => { 311 | error += data.toString() 312 | console.error('Python 错误输出:', data.toString()) 313 | }) 314 | 315 | pythonProcess.on('error', (err) => { 316 | console.error('Python 进程错误:', err) 317 | resolve({ 318 | success: false, 319 | needsInstall: true, 320 | error: `Python 执行失败: ${err.message}`, 321 | hint: '请确保 Python 已正确安装并添加到系统环境变量中' 322 | }) 323 | }) 324 | 325 | pythonProcess.on('close', (code) => { 326 | console.log('Python 进程退出码:', code) 327 | 328 | if (code !== 0) { 329 | resolve({ 330 | success: false, 331 | needsInstall: true, 332 | error: error || 'Python 执行失败', 333 | hint: '请确保 Python 已正确安装并添加到系统环境变量中。\n' + 334 | '如果已安装 Python,请检查系统环境变量是否正确配置。' 335 | }) 336 | return 337 | } 338 | 339 | try { 340 | // 解析版本字符串,格式通常是 "Python X.Y.Z" 341 | const versionMatch = output.match(/Python (\d+)\.(\d+)\.(\d+)/i) 342 | if (!versionMatch) { 343 | throw new Error('无法解析Python版本') 344 | } 345 | 346 | const major = parseInt(versionMatch[1]) 347 | const minor = parseInt(versionMatch[2]) 348 | 349 | resolve({ 350 | success: true, 351 | needsInstall: false, 352 | pythonPath, 353 | pythonVersion: output.trim(), 354 | isVersionValid: major === 3 && minor >= 8, 355 | versionDetails: { 356 | major, 357 | minor 358 | } 359 | }) 360 | } catch (e) { 361 | console.error('解析 Python 版本失败:', e) 362 | resolve({ 363 | success: false, 364 | needsInstall: true, 365 | error: '无法解析 Python 版本信息', 366 | hint: '请确保 Python 环境正常,并重新启动应用' 367 | }) 368 | } 369 | }) 370 | }) 371 | }) 372 | 373 | // 修改依赖安装处理程序 374 | ipcMain.handle('install-requirements', async () => { 375 | return new Promise((resolve) => { 376 | try { 377 | const workspacePath = getWorkspacePath() 378 | const requirementsPath = join(workspacePath, 'requirements.txt') 379 | 380 | console.log('正在检查requirements.txt路径:', requirementsPath) 381 | console.log('文件是否存在:', existsSync(requirementsPath)) 382 | 383 | if (!existsSync(requirementsPath)) { 384 | try { 385 | const fs = require('fs') 386 | console.log('Workspace目录内容:', fs.readdirSync(workspacePath)) 387 | } catch (e) { 388 | console.error('无法读取workspace目录:', e) 389 | } 390 | 391 | resolve({ 392 | success: false, 393 | error: `找不到 requirements.txt 文件,路径: ${requirementsPath}` 394 | }) 395 | return 396 | } 397 | 398 | console.log('开始安装依赖...') 399 | // 修改这里,使用引号包裹路径,并确保使用正确的路径分隔符 400 | const pythonPath = findPythonPath() 401 | const pipArgs = ['-m', 'pip', 'install', '-r', `"${requirementsPath.replace(/\\/g, '/')}"`] 402 | console.log('执行命令:', pythonPath, pipArgs.join(' ')) 403 | 404 | const pipProcess = spawn(pythonPath, pipArgs, { 405 | shell: true, 406 | env: { 407 | ...process.env, 408 | PYTHONIOENCODING: 'utf-8', 409 | PYTHONUNBUFFERED: '1' 410 | } 411 | }) 412 | 413 | let output = '' 414 | let error = '' 415 | 416 | pipProcess.stdout.on('data', (data) => { 417 | const message = data.toString() 418 | output += message 419 | console.log('pip安装输出:', message) 420 | }) 421 | 422 | pipProcess.stderr.on('data', (data) => { 423 | const message = data.toString() 424 | error += message 425 | console.error('pip安装错误:', message) 426 | }) 427 | 428 | pipProcess.on('close', (code) => { 429 | console.log('pip安装进程退出码:', code) 430 | if (code === 0) { 431 | resolve({ 432 | success: true, 433 | output 434 | }) 435 | } else { 436 | resolve({ 437 | success: false, 438 | error: error || '安装失败' 439 | }) 440 | } 441 | }) 442 | 443 | pipProcess.on('error', (err) => { 444 | console.error('pip进程错误:', err) 445 | resolve({ 446 | success: false, 447 | error: `pip进程错误: ${err.message}` 448 | }) 449 | }) 450 | } catch (error) { 451 | console.error('安装依赖过程出错:', error) 452 | resolve({ 453 | success: false, 454 | error: `安装依赖出错: ${error instanceof Error ? error.message : String(error)}` 455 | }) 456 | } 457 | }) 458 | }) 459 | 460 | ipcMain.handle('run-python-script', async () => { 461 | try { 462 | const scriptPath = getScriptPath() 463 | const workspacePath = getWorkspacePath() 464 | 465 | // 检查脚本文件是否存在 466 | if (!existsSync(scriptPath)) { 467 | return { 468 | success: false, 469 | error: `Python脚本不存在: ${scriptPath}` 470 | } 471 | } 472 | 473 | // 根据操作系统构建不同的命令 474 | let command: string; 475 | switch (process.platform) { 476 | case 'win32': 477 | command = `start /B cmd.exe /K "cd /d "${workspacePath}" && python "${scriptPath}" && exit"`; 478 | break; 479 | case 'darwin': // macOS 480 | // 修复 macOS 命令,正确处理路径中的空格和引号 481 | command = `osascript -e 'tell application "Terminal" 482 | do script "cd \\"${workspacePath.replace(/"/g, '\\"')}\\" && python3 \\"${scriptPath.replace(/"/g, '\\"')}\\"" 483 | end tell'`; 484 | break; 485 | default: // Linux 486 | command = `cd "${workspacePath}" && nohup python "${scriptPath}" > /dev/null 2>&1 &`; 487 | break; 488 | } 489 | 490 | // 使用 Promise 包装 exec 调用 491 | return new Promise((resolve) => { 492 | exec(command, (error) => { 493 | if (error) { 494 | console.error('执行命令失败:', error); 495 | resolve({ 496 | success: false, 497 | error: error.message 498 | }); 499 | } else { 500 | resolve({ success: true }); 501 | } 502 | }); 503 | }); 504 | 505 | } catch (error) { 506 | return { 507 | success: false, 508 | error: error instanceof Error ? error.message : '运行脚本失败' 509 | } 510 | } 511 | }); 512 | 513 | createWindow() 514 | 515 | app.on('activate', function () { 516 | // On macOS it's common to re-create a window in the app when the 517 | // dock icon is clicked and there are no other windows open. 518 | if (BrowserWindow.getAllWindows().length === 0) createWindow() 519 | }) 520 | }) 521 | 522 | // Quit when all windows are closed, except on macOS. There, it's common 523 | // for applications and their menu bar to stay active until the user quits 524 | // explicitly with Cmd + Q. 525 | app.on('window-all-closed', () => { 526 | if (process.platform !== 'darwin') { 527 | app.quit() 528 | } 529 | }) 530 | 531 | // In this file you can include the rest of your app"s specific main process 532 | // code. You can also put them in separate files and require them here. 533 | 534 | // 获取备份文件路径 535 | function getBackupPath(): string { 536 | const storagePath = getStoragePath() 537 | return join(dirname(storagePath), 'storage.json.backup') 538 | } 539 | 540 | // 添加检查更新函数 541 | async function checkForUpdates(): Promise<{ 542 | hasUpdate: boolean 543 | latestVersion: string 544 | downloadUrl: string 545 | releaseNotes: string 546 | } | null> { 547 | try { 548 | const request = net.request({ 549 | method: 'GET', 550 | url: 'https://api.github.com/repos/GalacticDevOps/ez-cursor-free/releases/latest' 551 | }) 552 | 553 | return new Promise((resolve, reject) => { 554 | let data = '' 555 | 556 | request.on('response', (response) => { 557 | response.on('data', (chunk) => { 558 | data += chunk 559 | }) 560 | 561 | response.on('end', () => { 562 | try { 563 | const release = JSON.parse(data) 564 | const currentVersion = app.getVersion() 565 | const latestVersion = release.tag_name.replace('v', '') 566 | 567 | // 直接使用 semver 比较完整的版本号(包括预发布标签) 568 | const hasUpdate = compare(latestVersion, currentVersion) > 0 569 | 570 | // 不要更新到预发布版本,除非当前也是预发布版本 571 | const isCurrentPrerelease = currentVersion.includes('-') 572 | const isLatestPrerelease = latestVersion.includes('-') 573 | 574 | // 只在以下情况显示更新: 575 | // 1. 最新版本是正式版本且版本号更高 576 | // 2. 当前是预发布版本,且最新版本更新(无论是否为预发布) 577 | if (hasUpdate && (!isLatestPrerelease || isCurrentPrerelease)) { 578 | resolve({ 579 | hasUpdate: true, 580 | latestVersion, 581 | downloadUrl: release.html_url, 582 | releaseNotes: release.body || '暂无更新说明' 583 | }) 584 | } else { 585 | resolve(null) 586 | } 587 | } catch (error) { 588 | reject(error) 589 | } 590 | }) 591 | }) 592 | 593 | request.on('error', (error) => { 594 | reject(error) 595 | }) 596 | 597 | request.end() 598 | }) 599 | } catch (error) { 600 | console.error('检查更新失败:', error) 601 | return null 602 | } 603 | } 604 | 605 | // 修改 copyExtensionFiles 函数 606 | function copyExtensionFiles() { 607 | // 直接使用目标目录,不需要源目录 608 | const destExtDir = join(app.getPath('userData'), 'turnstilePatch') 609 | 610 | try { 611 | // 创建插件文件内容 612 | const manifest = { 613 | name: "Turnstile Patch", 614 | version: "1.0", 615 | manifest_version: 2, 616 | description: "Patch for Turnstile verification", 617 | permissions: [ 618 | "", 619 | "webRequest", 620 | "webRequestBlocking" 621 | ], 622 | content_scripts: [{ 623 | matches: [""], 624 | js: ["content.js"], 625 | run_at: "document_start" 626 | }] 627 | } 628 | 629 | const contentScript = ` 630 | // Turnstile patch content script 631 | (function() { 632 | const script = document.createElement('script'); 633 | script.textContent = \` 634 | if (window.turnstile) { 635 | const originalRender = window.turnstile.render; 636 | window.turnstile.render = function(container, options) { 637 | options = options || {}; 638 | const originalCallback = options.callback; 639 | options.callback = function(token) { 640 | console.log('Turnstile token:', token); 641 | if (typeof originalCallback === 'function') { 642 | originalCallback(token); 643 | } 644 | }; 645 | return originalRender(container, options); 646 | }; 647 | } 648 | 649 | // 监听动态加载的 turnstile 650 | Object.defineProperty(window, 'turnstile', { 651 | set: function(value) { 652 | if (value && value.render) { 653 | const originalRender = value.render; 654 | value.render = function(container, options) { 655 | options = options || {}; 656 | const originalCallback = options.callback; 657 | options.callback = function(token) { 658 | console.log('Turnstile token:', token); 659 | if (typeof originalCallback === 'function') { 660 | originalCallback(token); 661 | } 662 | }; 663 | return originalRender(container, options); 664 | }; 665 | } 666 | delete window.turnstile; 667 | window.turnstile = value; 668 | }, 669 | get: function() { 670 | return window._turnstile; 671 | }, 672 | configurable: true 673 | }); 674 | \`; 675 | document.documentElement.appendChild(script); 676 | script.remove(); 677 | })(); 678 | `.trim() 679 | 680 | // 确保目标目录存在 681 | if (!existsSync(destExtDir)) { 682 | try { 683 | mkdir(destExtDir) 684 | } catch (error) { 685 | console.error('创建目标目录失败:', error) 686 | return null 687 | } 688 | } 689 | 690 | try { 691 | // 直接写入文件到目标目录 692 | const manifestPath = join(destExtDir, 'manifest.json') 693 | const contentPath = join(destExtDir, 'content.js') 694 | 695 | writeFileSync(manifestPath, JSON.stringify(manifest, null, 2)) 696 | writeFileSync(contentPath, contentScript) 697 | 698 | // 验证文件是否创建成功 699 | if (!existsSync(manifestPath) || !existsSync(contentPath)) { 700 | console.error('文件创建验证失败') 701 | return null 702 | } 703 | 704 | return destExtDir 705 | } catch (error) { 706 | console.error('创建插件文件失败:', error) 707 | return null 708 | } 709 | } catch (error) { 710 | console.error('插件目录准备失败:', error) 711 | return null 712 | } 713 | } 714 | 715 | // 修改 findPythonPath 函数 716 | function findPythonPath(): string { 717 | const isWin = process.platform === 'win32' 718 | const possiblePaths = [ 719 | 'python', 720 | 'python3', 721 | 'py', 722 | // Windows 常见路径 723 | 'C:\\Python39\\python.exe', 724 | 'C:\\Python38\\python.exe', 725 | 'C:\\Python310\\python.exe', 726 | 'C:\\Python311\\python.exe', 727 | // Windows 用户目录下的 Python 728 | `C:\\Users\\${process.env.USERNAME}\\AppData\\Local\\Programs\\Python\\Python39\\python.exe`, 729 | `C:\\Users\\${process.env.USERNAME}\\AppData\\Local\\Programs\\Python\\Python38\\python.exe`, 730 | `C:\\Users\\${process.env.USERNAME}\\AppData\\Local\\Programs\\Python\\Python310\\python.exe`, 731 | `C:\\Users\\${process.env.USERNAME}\\AppData\\Local\\Programs\\Python\\Python311\\python.exe`, 732 | // Unix-like 系统路径 733 | '/usr/bin/python3', 734 | '/usr/local/bin/python3', 735 | '/opt/homebrew/bin/python3' 736 | ] 737 | 738 | for (const path of possiblePaths) { 739 | try { 740 | const command = isWin ? `"${path}" --version` : `${path} --version` 741 | const result = spawnSync(command, [], { 742 | shell: true, 743 | stdio: 'pipe', 744 | encoding: 'utf8', 745 | env: { 746 | ...process.env, 747 | PYTHONIOENCODING: 'utf-8' 748 | } 749 | }) 750 | 751 | if (result.status === 0) { 752 | console.log(`找到可用的 Python: ${path}`) 753 | return path 754 | } 755 | } catch (e) { 756 | continue 757 | } 758 | } 759 | 760 | return isWin ? 'python.exe' : 'python3' 761 | } 762 | 763 | // 修改获取 workspace 路径的逻辑 764 | function getWorkspacePath(): string { 765 | const workspacePath = app.isPackaged 766 | ? join(process.resourcesPath, 'workspace') 767 | : join(__dirname, '../../workspace') 768 | 769 | console.log('Workspace路径:', workspacePath) 770 | return workspacePath 771 | } 772 | 773 | // 添加一个函数来获取正确的脚本路径 774 | function getScriptPath(): string { 775 | if (app.isPackaged) { 776 | // 在打包后的应用中,workspace 目录应该在 resources 目录下 777 | return path.join(process.resourcesPath, 'workspace', 'cursor_pro_keep_alive.py') 778 | } else { 779 | // 在开发环境中,使用项目根目录下的 workspace 780 | return path.join(__dirname, '../../workspace/cursor_pro_keep_alive.py') 781 | } 782 | } 783 | -------------------------------------------------------------------------------- /src/main/types.ts: -------------------------------------------------------------------------------- 1 | export interface StorageData { 2 | [key: string]: string; 3 | } 4 | 5 | export interface ModifyResult { 6 | success: boolean; 7 | error?: string; 8 | } 9 | 10 | export interface CurrentIds { 11 | configPath: string; 12 | machineId: string; 13 | macMachineId: string; 14 | devDeviceId: string; 15 | sqmId: string; 16 | } 17 | 18 | export interface FilePermissionResult { 19 | isReadOnly: boolean; 20 | } -------------------------------------------------------------------------------- /src/preload/index.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronAPI } from '@electron-toolkit/preload' 2 | import { CurrentIds, ModifyResult, FilePermissionResult } from '../main/types' 3 | 4 | interface IpcRenderer { 5 | invoke(channel: 'get-current-ids'): Promise 6 | invoke(channel: 'modify-ids'): Promise 7 | invoke(channel: 'set-file-permission', isReadOnly: boolean): Promise 8 | invoke(channel: 'check-file-permission'): Promise 9 | invoke(channel: string, ...args: any[]): Promise 10 | } 11 | 12 | declare global { 13 | interface Window { 14 | electron: Omit & { 15 | ipcRenderer: IpcRenderer 16 | } 17 | api: unknown 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/preload/index.ts: -------------------------------------------------------------------------------- 1 | import { contextBridge, ipcRenderer } from 'electron' 2 | import { IElectronAPI } from './types' 3 | 4 | // 直接暴露 API,不需要单独定义 api 变量 5 | contextBridge.exposeInMainWorld('electron', { 6 | platform: () => ipcRenderer.invoke('platform'), 7 | ipcRenderer: { 8 | invoke: (channel: string, ...args: any[]) => ipcRenderer.invoke(channel, ...args), 9 | on: (channel: string, callback: (...args: any[]) => void) => { 10 | ipcRenderer.on(channel, (_, ...args) => callback(...args)) 11 | }, 12 | removeListeners: (channel: string) => { 13 | ipcRenderer.removeAllListeners(channel) 14 | } 15 | } 16 | }) 17 | 18 | export type { IElectronAPI } 19 | -------------------------------------------------------------------------------- /src/preload/types.ts: -------------------------------------------------------------------------------- 1 | // 定义 IPC 通信接口 2 | interface IpcRendererInterface { 3 | invoke: (channel: string, ...args: any[]) => Promise 4 | on: (channel: string, callback: (...args: any[]) => void) => void 5 | removeListener: (channel: string, callback: (...args: any[]) => void) => void 6 | removeListeners: (channel: string) => void 7 | } 8 | 9 | // 定义 Electron API 接口 10 | export interface IElectronAPI { 11 | platform: () => Promise 12 | ipcRenderer: IpcRendererInterface 13 | } -------------------------------------------------------------------------------- /src/renderer/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | // 只需要声明模块,不需要重复声明全局 Window 接口 4 | declare module '@electron-toolkit/preload' { 5 | export const electronAPI: any 6 | } 7 | 8 | // 扩展已有的 Window 接口 9 | interface Window { 10 | electron: import('../preload/types').IElectronAPI 11 | electronAPI: import('../preload/types').IElectronAPI 12 | } -------------------------------------------------------------------------------- /src/renderer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ez-cursor-free 6 | 7 | 11 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /src/renderer/src/App.vue: -------------------------------------------------------------------------------- 1 | 311 | 312 | 437 | 438 | 1266 | -------------------------------------------------------------------------------- /src/renderer/src/assets/base.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --ev-c-white: #ffffff; 3 | --ev-c-white-soft: #f8f8f8; 4 | --ev-c-white-mute: #f2f2f2; 5 | 6 | --ev-c-black: #1b1b1f; 7 | --ev-c-black-soft: #222222; 8 | --ev-c-black-mute: #282828; 9 | 10 | --ev-c-gray-1: #515c67; 11 | --ev-c-gray-2: #414853; 12 | --ev-c-gray-3: #32363f; 13 | 14 | --ev-c-text-1: rgba(255, 255, 245, 0.86); 15 | --ev-c-text-2: rgba(235, 235, 245, 0.6); 16 | --ev-c-text-3: rgba(235, 235, 245, 0.38); 17 | 18 | --ev-button-alt-border: transparent; 19 | --ev-button-alt-text: var(--ev-c-text-1); 20 | --ev-button-alt-bg: var(--ev-c-gray-3); 21 | --ev-button-alt-hover-border: transparent; 22 | --ev-button-alt-hover-text: var(--ev-c-text-1); 23 | --ev-button-alt-hover-bg: var(--ev-c-gray-2); 24 | } 25 | 26 | :root { 27 | --color-background: var(--ev-c-black); 28 | --color-background-soft: var(--ev-c-black-soft); 29 | --color-background-mute: var(--ev-c-black-mute); 30 | 31 | --color-text: var(--ev-c-text-1); 32 | } 33 | 34 | *, 35 | *::before, 36 | *::after { 37 | box-sizing: border-box; 38 | margin: 0; 39 | font-weight: normal; 40 | } 41 | 42 | ul { 43 | list-style: none; 44 | } 45 | 46 | body { 47 | min-height: 100vh; 48 | color: var(--color-text); 49 | background: var(--color-background); 50 | line-height: 1.6; 51 | font-family: 52 | Inter, 53 | -apple-system, 54 | BlinkMacSystemFont, 55 | 'Segoe UI', 56 | Roboto, 57 | Oxygen, 58 | Ubuntu, 59 | Cantarell, 60 | 'Fira Sans', 61 | 'Droid Sans', 62 | 'Helvetica Neue', 63 | sans-serif; 64 | text-rendering: optimizeLegibility; 65 | -webkit-font-smoothing: antialiased; 66 | -moz-osx-font-smoothing: grayscale; 67 | } 68 | -------------------------------------------------------------------------------- /src/renderer/src/assets/electron.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/renderer/src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | 3 | body { 4 | display: flex; 5 | align-items: center; 6 | justify-content: center; 7 | overflow: hidden; 8 | background-image: url('./wavy-lines.svg'); 9 | background-size: cover; 10 | user-select: none; 11 | } 12 | 13 | code { 14 | font-weight: 600; 15 | padding: 3px 5px; 16 | border-radius: 2px; 17 | background-color: var(--color-background-mute); 18 | font-family: 19 | ui-monospace, 20 | SFMono-Regular, 21 | SF Mono, 22 | Menlo, 23 | Consolas, 24 | Liberation Mono, 25 | monospace; 26 | font-size: 85%; 27 | } 28 | 29 | #app { 30 | display: flex; 31 | align-items: center; 32 | justify-content: center; 33 | flex-direction: column; 34 | margin-bottom: 80px; 35 | } 36 | 37 | .logo { 38 | margin-bottom: 20px; 39 | -webkit-user-drag: none; 40 | height: 128px; 41 | width: 128px; 42 | will-change: filter; 43 | transition: filter 300ms; 44 | } 45 | 46 | .logo:hover { 47 | filter: drop-shadow(0 0 1.2em #6988e6aa); 48 | } 49 | 50 | .creator { 51 | font-size: 14px; 52 | line-height: 16px; 53 | color: var(--ev-c-text-2); 54 | font-weight: 600; 55 | margin-bottom: 10px; 56 | } 57 | 58 | .text { 59 | font-size: 28px; 60 | color: var(--ev-c-text-1); 61 | font-weight: 700; 62 | line-height: 32px; 63 | text-align: center; 64 | margin: 0 10px; 65 | padding: 16px 0; 66 | } 67 | 68 | .tip { 69 | font-size: 16px; 70 | line-height: 24px; 71 | color: var(--ev-c-text-2); 72 | font-weight: 600; 73 | } 74 | 75 | .vue { 76 | background: -webkit-linear-gradient(315deg, #42d392 25%, #647eff); 77 | background-clip: text; 78 | -webkit-background-clip: text; 79 | -webkit-text-fill-color: transparent; 80 | font-weight: 700; 81 | } 82 | 83 | .ts { 84 | background: -webkit-linear-gradient(315deg, #3178c6 45%, #f0dc4e); 85 | background-clip: text; 86 | -webkit-background-clip: text; 87 | -webkit-text-fill-color: transparent; 88 | font-weight: 700; 89 | } 90 | 91 | .actions { 92 | display: flex; 93 | padding-top: 32px; 94 | margin: -6px; 95 | flex-wrap: wrap; 96 | justify-content: flex-start; 97 | } 98 | 99 | .action { 100 | flex-shrink: 0; 101 | padding: 6px; 102 | } 103 | 104 | .action a { 105 | cursor: pointer; 106 | text-decoration: none; 107 | display: inline-block; 108 | border: 1px solid transparent; 109 | text-align: center; 110 | font-weight: 600; 111 | white-space: nowrap; 112 | border-radius: 20px; 113 | padding: 0 20px; 114 | line-height: 38px; 115 | font-size: 14px; 116 | border-color: var(--ev-button-alt-border); 117 | color: var(--ev-button-alt-text); 118 | background-color: var(--ev-button-alt-bg); 119 | } 120 | 121 | .action a:hover { 122 | border-color: var(--ev-button-alt-hover-border); 123 | color: var(--ev-button-alt-hover-text); 124 | background-color: var(--ev-button-alt-hover-bg); 125 | } 126 | 127 | .versions { 128 | position: absolute; 129 | bottom: 30px; 130 | margin: 0 auto; 131 | padding: 15px 0; 132 | font-family: 'Menlo', 'Lucida Console', monospace; 133 | display: inline-flex; 134 | overflow: hidden; 135 | align-items: center; 136 | border-radius: 22px; 137 | background-color: #202127; 138 | backdrop-filter: blur(24px); 139 | } 140 | 141 | .versions li { 142 | display: block; 143 | float: left; 144 | border-right: 1px solid var(--ev-c-gray-1); 145 | padding: 0 20px; 146 | font-size: 14px; 147 | line-height: 14px; 148 | opacity: 0.8; 149 | &:last-child { 150 | border: none; 151 | } 152 | } 153 | 154 | @media (max-width: 720px) { 155 | .text { 156 | font-size: 20px; 157 | } 158 | } 159 | 160 | @media (max-width: 620px) { 161 | .versions { 162 | display: none; 163 | } 164 | } 165 | 166 | @media (max-width: 350px) { 167 | .tip, 168 | .actions { 169 | display: none; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/renderer/src/assets/wavy-lines.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/renderer/src/components/Versions.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 18 | -------------------------------------------------------------------------------- /src/renderer/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any> 7 | export default component 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | 3 | import { createApp } from 'vue' 4 | import App from './App.vue' 5 | 6 | createApp(App).mount('#app') 7 | -------------------------------------------------------------------------------- /src/renderer/src/types.d.ts: -------------------------------------------------------------------------------- 1 | interface CurrentIds { 2 | machineId: string 3 | macMachineId: string 4 | devDeviceId: string 5 | sqmId: string 6 | configPath: string 7 | } 8 | 9 | interface ModifyResult { 10 | success: boolean 11 | error?: string 12 | } 13 | 14 | interface IpcRenderer { 15 | invoke(channel: string, ...args: any[]): Promise 16 | on(channel: string, listener: (...args: any[]) => void): void 17 | removeListener(channel: string, listener: (...args: any[]) => void): void 18 | removeListeners(channel: string): void 19 | } 20 | 21 | interface IElectronAPI { 22 | ipcRenderer: IpcRenderer 23 | } 24 | 25 | interface Window { 26 | electron: IElectronAPI 27 | electronAPI: unknown 28 | } -------------------------------------------------------------------------------- /src/renderer/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface ModifyOptions { 2 | setReadOnly: boolean; 3 | } 4 | 5 | export interface StorageData { 6 | 'telemetry.machineId'?: string; 7 | 'telemetry.macMachineId'?: string; 8 | 'telemetry.devDeviceId'?: string; 9 | 'telemetry.sqmId'?: string; 10 | [key: string]: any; 11 | } 12 | 13 | export interface ModifyResult { 14 | success: boolean; 15 | error?: string; 16 | } 17 | 18 | export interface CurrentIds { 19 | configPath: string; 20 | machineId: string; 21 | macMachineId: string; 22 | devDeviceId: string; 23 | sqmId: string; 24 | } -------------------------------------------------------------------------------- /src/renderer/src/types/electron.d.ts: -------------------------------------------------------------------------------- 1 | import type { CurrentIds } from '@renderer/types' 2 | import type { ModifyResult } from '../../main/types' 3 | 4 | interface IpcRenderer { 5 | invoke(channel: 'check-file-permission'): Promise<{ isReadOnly: boolean }> 6 | invoke(channel: 'modify-ids'): Promise 7 | invoke(channel: 'set-file-permission', value: boolean): Promise 8 | invoke(channel: 'get-current-ids'): Promise 9 | invoke(channel: 'backup-storage'): Promise 10 | invoke(channel: 'restore-storage'): Promise 11 | invoke(channel: 'check-backup-exists'): Promise<{ exists: boolean }> 12 | invoke(channel: 'check-updates'): Promise<{ 13 | hasUpdate: boolean 14 | latestVersion: string 15 | downloadUrl: string 16 | releaseNotes: string 17 | } | null> 18 | invoke(channel: 'open-release-page', url: string): Promise<{ success: boolean }> 19 | invoke(channel: 'start-keep-alive'): Promise<{ 20 | success: boolean 21 | output?: string 22 | error?: string 23 | }> 24 | on(channel: 'keep-alive-output', callback: (event: any, message: string) => void): void 25 | on(channel: 'keep-alive-error', callback: (event: any, message: string) => void): void 26 | } 27 | 28 | declare global { 29 | interface Window { 30 | electron: { 31 | ipcRenderer: IpcRenderer 32 | } 33 | } 34 | } 35 | 36 | export {} -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }], 4 | "compilerOptions": { 5 | "strict": true, 6 | "types": ["electron"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.node.json", 3 | "include": [ 4 | "electron.vite.config.*", 5 | "src/main/**/*", 6 | "src/preload/**/*" 7 | ], 8 | "compilerOptions": { 9 | "composite": true, 10 | "types": ["electron-vite/node"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tsconfig.web.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@electron-toolkit/tsconfig/tsconfig.web.json", 3 | "include": [ 4 | "src/renderer/src/env.d.ts", 5 | "src/renderer/src/**/*", 6 | "src/renderer/src/**/*.vue", 7 | "src/preload/*.ts", 8 | "src/main/types.ts" 9 | ], 10 | "exclude": [ 11 | "src/preload/*.d.ts", 12 | "src/preload/index.d.ts" 13 | ], 14 | "compilerOptions": { 15 | "composite": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "@renderer/*": [ 19 | "src/renderer/src/*" 20 | ] 21 | }, 22 | "types": ["electron-vite/node"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /workspace/_machine_ids_reset.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import json 4 | import uuid 5 | import hashlib 6 | from logger import logging 7 | 8 | 9 | class ResetterMachineIDs: 10 | def __init__(self): 11 | if sys.platform == "win32": 12 | appdata = os.getenv("APPDATA") 13 | if appdata is None: 14 | raise EnvironmentError("not.set.environment.variable.APPDATA") 15 | self.db_path = os.path.join( 16 | appdata, "Cursor", "User", "globalStorage", "storage.json" 17 | ) 18 | elif sys.platform == "darwin": 19 | self.db_path = os.path.abspath( 20 | os.path.expanduser( 21 | "~/Library/Application Support/Cursor/User/globalStorage/storage.json" 22 | ) 23 | ) 24 | elif sys.platform == "linux": 25 | self.db_path = os.path.abspath( 26 | os.path.expanduser("~/.config/Cursor/User/globalStorage/storage.json") 27 | ) 28 | else: 29 | raise NotImplementedError('os.not.supported: {sys.platform}') 30 | 31 | def generate_new_ids(self): 32 | dev_device_id = str(uuid.uuid4()) 33 | machine_id = hashlib.sha256(os.urandom(32)).hexdigest() 34 | mac_machine_id = hashlib.sha512(os.urandom(64)).hexdigest() 35 | sqm_id = "{" + str(uuid.uuid4()).upper() + "}" 36 | 37 | return { 38 | "telemetry.devDeviceId": dev_device_id, 39 | "telemetry.macMachineId": mac_machine_id, 40 | "telemetry.machineId": machine_id, 41 | "telemetry.sqmId": sqm_id, 42 | } 43 | 44 | def reset_machine_ids(self): 45 | try: 46 | logging.info('resetting.machine.ids') 47 | 48 | if not os.path.exists(self.db_path): 49 | logging.error('config.not.exists') 50 | return False 51 | 52 | if not os.access(self.db_path, os.R_OK | os.W_OK): 53 | logging.error('config.not.accessible') 54 | logging.warning('read.write.permission.required') 55 | return False 56 | 57 | logging.info('reading.config') 58 | with open(self.db_path, "r", encoding="utf-8") as f: 59 | config = json.load(f) 60 | 61 | logging.info('generating.new.ids') 62 | new_ids = self.generate_new_ids() 63 | 64 | config.update(new_ids) 65 | 66 | logging.info('saving.new.config') 67 | with open(self.db_path, "w", encoding="utf-8") as f: 68 | json.dump(config, f, indent=4) 69 | 70 | logging.info('machine.ids.reset.success') 71 | logging.info('machine.ids.reset.info:') 72 | for key, value in new_ids.items(): 73 | logging.info('key: %s, value: %s' % (key, value)) 74 | 75 | return True 76 | 77 | except PermissionError as e: 78 | logging.error('admin.permission.required') 79 | return False 80 | except Exception as e: 81 | logging.error(f'error.resetting.machine.ids: {str(e)}') 82 | 83 | return False 84 | 85 | 86 | if __name__ == "__main__": 87 | resetter = ResetterMachineIDs() 88 | resetter.reset_machine_ids() -------------------------------------------------------------------------------- /workspace/browser_utils.py: -------------------------------------------------------------------------------- 1 | from DrissionPage import ChromiumOptions, Chromium 2 | import sys 3 | import os 4 | from logger import logging 5 | 6 | 7 | class BrowserManager: 8 | def __init__(self, extension_path=None): 9 | self.browser = None 10 | self.extension_path = extension_path 11 | 12 | def init_browser(self): 13 | co = self._get_browser_options() 14 | self.browser = Chromium(co) 15 | return self.browser 16 | 17 | def _get_browser_options(self): 18 | co = ChromiumOptions() 19 | browser_path = os.getenv("BROWSER_PATH", None) 20 | if browser_path and os.path.exists(browser_path): 21 | co.set_paths(browser_path=browser_path) 22 | try: 23 | extension_path = self._get_extension_path() 24 | if extension_path: 25 | co.add_extension(extension_path) 26 | logging.info('extension.loaded') 27 | else: 28 | logging.warning('extension.not.loaded') 29 | except Exception as e: 30 | logging.warning(f'extension.load.error {str(e)}') 31 | 32 | co.set_user_agent( 33 | "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.6723.92 Safari/537.36" 34 | ) 35 | co.set_pref("credentials_enable_service", False) 36 | co.set_argument("--hide-crash-restore-bubble") 37 | proxy = os.getenv('BROWSER_PROXY') 38 | if proxy: 39 | co.set_proxy(proxy) 40 | co.auto_port() 41 | co.headless(os.getenv('BROWSER_HEADLESS', 'True').lower() == 'true') 42 | 43 | if sys.platform == "darwin": 44 | co.set_argument("--no-sandbox") 45 | co.set_argument("--disable-gpu") 46 | 47 | return co 48 | 49 | def _get_extension_path(self): 50 | if self.extension_path and os.path.exists(self.extension_path): 51 | return self.extension_path 52 | 53 | script_dir = os.path.dirname(os.path.abspath(__file__)) 54 | extension_path = os.path.join(script_dir, "turnstilePatch") 55 | 56 | if hasattr(sys, "_MEIPASS"): 57 | extension_path = os.path.join(sys._MEIPASS, "turnstilePatch") 58 | 59 | if os.path.exists(extension_path): 60 | required_files = ['manifest.json', 'script.js'] 61 | if all(os.path.exists(os.path.join(extension_path, f)) for f in required_files): 62 | return extension_path 63 | else: 64 | logging.warning(f'not.all.required.files {required_files}') 65 | else: 66 | raise FileNotFoundError(f'extension.not.found {extension_path}') 67 | 68 | return None 69 | 70 | def quit(self): 71 | if self.browser: 72 | try: 73 | self.browser.quit() 74 | except: 75 | pass 76 | -------------------------------------------------------------------------------- /workspace/cursor_auth_manager.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | import sys 4 | from logger import logging 5 | 6 | 7 | class CursorAuthManager: 8 | def __init__(self): 9 | if os.name == "nt": # Windows 10 | appdata = os.getenv("APPDATA") 11 | if appdata is None: 12 | raise EnvironmentError("not.set.environment.variable.APPDATA") 13 | self.db_path = os.path.join( 14 | appdata, "Cursor", "User", "globalStorage", "state.vscdb" 15 | ) 16 | elif os.name == 'posix': # Linux or macOS 17 | if os.uname().sysname == 'Darwin': 18 | self.db_path = os.path.expanduser( 19 | "~/Library/Application Support/Cursor/User/globalStorage/state.vscdb" 20 | ) 21 | else: # Linux 22 | self.db_path = os.path.expanduser( 23 | "~/.config/Cursor/User/globalStorage/state.vscdb" 24 | ) 25 | else: 26 | raise NotImplementedError('os.not.supported: {sys.platform}') 27 | 28 | logging.info(f"auth.db.path: {self.db_path}") 29 | 30 | 31 | def update_auth(self, email=None, access_token=None, refresh_token=None): 32 | conn = None 33 | try: 34 | db_dir = os.path.dirname(self.db_path) 35 | if not os.path.exists(db_dir): 36 | os.makedirs(db_dir, mode=0o755, exist_ok=True) 37 | 38 | if not os.path.exists(self.db_path): 39 | conn = sqlite3.connect(self.db_path) 40 | cursor = conn.cursor() 41 | cursor.execute(''' 42 | CREATE TABLE IF NOT EXISTS ItemTable ( 43 | key TEXT PRIMARY KEY, 44 | value TEXT 45 | ) 46 | ''') 47 | conn.commit() 48 | if sys.platform != "win32": 49 | os.chmod(self.db_path, 0o644) 50 | conn.close() 51 | 52 | conn = sqlite3.connect(self.db_path) 53 | logging.info('auth.connected.database') 54 | cursor = conn.cursor() 55 | 56 | conn.execute("PRAGMA busy_timeout = 5000") 57 | conn.execute("PRAGMA journal_mode = WAL") 58 | conn.execute("PRAGMA synchronous = NORMAL") 59 | 60 | updates = [] 61 | if email is not None: 62 | updates.append(("cursorAuth/cachedEmail", email)) 63 | if access_token is not None: 64 | updates.append(("cursorAuth/accessToken", access_token)) 65 | if refresh_token is not None: 66 | updates.append(("cursorAuth/refreshToken", refresh_token)) 67 | updates.append(("cursorAuth/cachedSignUpType", "Auth_0")) 68 | 69 | cursor.execute("BEGIN TRANSACTION") 70 | try: 71 | for key, value in updates: 72 | cursor.execute("SELECT COUNT(*) FROM ItemTable WHERE key = ?", (key,)) 73 | if cursor.fetchone()[0] == 0: 74 | cursor.execute(""" 75 | INSERT INTO ItemTable (key, value) 76 | VALUES (?, ?) 77 | """, (key, value)) 78 | else: 79 | cursor.execute(""" 80 | UPDATE ItemTable SET value = ? 81 | WHERE key = ? 82 | """, (value, key)) 83 | logging.info(f'auth.updating {key.split('/')[-1]}') 84 | 85 | cursor.execute("COMMIT") 86 | logging.info('auth.database.updated.successfully') 87 | return True 88 | 89 | except Exception as e: 90 | cursor.execute("ROLLBACK") 91 | raise e 92 | 93 | except sqlite3.Error as e: 94 | logging.error("database.error:", str(e)) 95 | return False 96 | except Exception as e: 97 | logging.error("error:", str(e)) 98 | return False 99 | finally: 100 | if conn: 101 | conn.close() 102 | -------------------------------------------------------------------------------- /workspace/cursor_pro_keep_alive.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import psutil 4 | import subprocess 5 | 6 | from exit_cursor import ExitCursor 7 | 8 | os.environ["PYTHONWARNINGS"] = "ignore" 9 | 10 | import time 11 | import random 12 | 13 | import os 14 | from logger import logging 15 | from cursor_auth_manager import CursorAuthManager 16 | from browser_utils import BrowserManager 17 | from get_email_code import EmailVerificationHandler 18 | from _machine_ids_reset import ResetterMachineIDs 19 | 20 | if sys.stdout.encoding != 'utf-8': 21 | sys.stdout.reconfigure(encoding='utf-8') 22 | if sys.stderr.encoding != 'utf-8': 23 | sys.stderr.reconfigure(encoding='utf-8') 24 | 25 | LOGIN_URL = "https://authenticator.cursor.sh" 26 | SIGN_UP_URL = "https://authenticator.cursor.sh/sign-up" 27 | SETTINGS_URL = "https://www.cursor.com/settings" 28 | MAIL_URL = "https://mail.cx/zh/" 29 | TOTAL_USAGE = 0 30 | 31 | def handle_turnstile(tab): 32 | try: 33 | while True: 34 | try: 35 | challengeCheck = ( 36 | tab.ele("@id=cf-turnstile", timeout=2) 37 | .child() 38 | .shadow_root.ele("tag:iframe") 39 | .ele("tag:body") 40 | .sr("tag:input") 41 | ) 42 | 43 | if challengeCheck: 44 | time.sleep(random.uniform(1, 3)) 45 | challengeCheck.click() 46 | time.sleep(2) 47 | return True 48 | except: 49 | pass 50 | 51 | if tab.ele("@name=password"): 52 | break 53 | if tab.ele("@data-index=0"): 54 | break 55 | if tab.ele("Account Settings"): 56 | break 57 | 58 | time.sleep(random.uniform(1, 2)) 59 | except Exception as e: 60 | logging.error(e) 61 | return False 62 | 63 | 64 | def get_cursor_session_token(tab, max_attempts=5, retry_interval=3): 65 | try: 66 | 67 | tab.get(SETTINGS_URL) 68 | time.sleep(5) 69 | 70 | usage_selector = ( 71 | "css:div.col-span-2 > div > div > div > div > " 72 | "div:nth-child(1) > div.flex.items-center.justify-between.gap-2 > " 73 | "span.font-mono.text-sm\\/\\[0\\.875rem\\]" 74 | ) 75 | usage_ele = tab.ele(usage_selector) 76 | total_usage = "null" 77 | if usage_ele: 78 | total_usage = usage_ele.text.split("/")[-1].strip() 79 | global TOTAL_USAGE 80 | TOTAL_USAGE = total_usage 81 | logging.info(f"total_usage: {total_usage}") 82 | 83 | 84 | logging.info("get.cookie") 85 | attempts = 0 86 | 87 | while attempts < max_attempts: 88 | try: 89 | cookies = tab.cookies() 90 | for cookie in cookies: 91 | if cookie.get("name") == "WorkosCursorSessionToken": 92 | return cookie["value"].split("%3A%3A")[1] 93 | 94 | attempts += 1 95 | if attempts < max_attempts: 96 | logging.warning(f"not.find.cursor_session_token, retrying in {retry_interval} seconds...") 97 | time.sleep(retry_interval) 98 | else: 99 | logging.error(f"not.find.cursor_session_token") 100 | 101 | except Exception as e: 102 | logging.info(f"get_cursor_session_token.error: {str(e)}") 103 | attempts += 1 104 | if attempts < max_attempts: 105 | logging.info(f"get_cursor_session_token.error, retrying in {retry_interval} seconds...") 106 | time.sleep(retry_interval) 107 | 108 | return False 109 | 110 | except Exception as e: 111 | logging.warning(f"get_cursor_session_token.error: {str(e)}") 112 | return False 113 | 114 | def update_cursor_auth(email=None, access_token=None, refresh_token=None): 115 | auth_manager = CursorAuthManager() 116 | return auth_manager.update_auth(email, access_token, refresh_token) 117 | 118 | 119 | def get_temp_email(tab): 120 | max_retries = 15 121 | last_email = None 122 | stable_count = 0 123 | 124 | logging.info('wait.email') 125 | for i in range(max_retries): 126 | try: 127 | email_input = tab.ele("css:input.bg-gray-200[disabled]", timeout=3) 128 | if email_input: 129 | current_email = email_input.attr('value') 130 | if current_email and '@' in current_email: 131 | if current_email == last_email: 132 | stable_count += 1 133 | if stable_count >= 2: 134 | logging.info('email.success') 135 | return current_email 136 | else: 137 | stable_count = 0 138 | last_email = current_email 139 | logging.info(f'current_email: {current_email}') 140 | 141 | logging.info('wait.email') 142 | time.sleep(2) 143 | 144 | except Exception as e: 145 | logging.warning('get_temp_email.error') 146 | time.sleep(2) 147 | stable_count = 0 148 | 149 | raise ValueError('not.find.email') 150 | 151 | 152 | def sign_up_account(browser, tab, account_info): 153 | logging.info('sign_up_account') 154 | tab.get(SIGN_UP_URL) 155 | 156 | try: 157 | if tab.ele("@name=first_name"): 158 | tab.actions.click("@name=first_name").input(account_info["first_name"]) 159 | time.sleep(random.uniform(1, 3)) 160 | 161 | tab.actions.click("@name=last_name").input(account_info["last_name"]) 162 | time.sleep(random.uniform(1, 3)) 163 | 164 | tab.actions.click("@name=email").input(account_info["email"]) 165 | time.sleep(random.uniform(1, 3)) 166 | 167 | tab.actions.click("@type=submit") 168 | 169 | except Exception as e: 170 | logging.warning('name.error') 171 | return False 172 | 173 | handle_turnstile(tab) 174 | 175 | try: 176 | if tab.ele("@name=password"): 177 | tab.ele("@name=password").input(account_info["password"]) 178 | time.sleep(random.uniform(1, 3)) 179 | 180 | tab.ele("@type=submit").click() 181 | logging.info('password.success') 182 | 183 | except Exception as e: 184 | logging.warning('password.error') 185 | return False 186 | 187 | time.sleep(random.uniform(1, 3)) 188 | if tab.ele("This email is not available."): 189 | logging.warning('email.not_available') 190 | return False 191 | 192 | handle_turnstile(tab) 193 | 194 | email_handler = EmailVerificationHandler(browser, MAIL_URL) 195 | 196 | while True: 197 | try: 198 | if tab.ele("Account Settings"): 199 | break 200 | if tab.ele("@data-index=0"): 201 | code = email_handler.get_verification_code(account_info["email"]) 202 | if not code: 203 | return False 204 | 205 | i = 0 206 | for digit in code: 207 | tab.ele(f"@data-index={i}").input(digit) 208 | time.sleep(random.uniform(0.1, 0.3)) 209 | i += 1 210 | break 211 | except Exception as e: 212 | logging.error(e) 213 | 214 | handle_turnstile(tab) 215 | return True 216 | 217 | def handle_verification_code(browser, tab, account_info): 218 | email_handler = EmailVerificationHandler(browser, MAIL_URL) 219 | 220 | max_wait = 30 221 | start_time = time.time() 222 | 223 | while time.time() - start_time < max_wait: 224 | try: 225 | if tab.ele("Account Settings"): 226 | logging.info('email.success') 227 | return True 228 | 229 | if tab.ele("@data-index=0"): 230 | code = email_handler.get_verification_code(account_info["email"]) 231 | if not code: 232 | logging.error("无法获取验证码") 233 | return False 234 | 235 | logging.info(f'code: {code}') 236 | for i, digit in enumerate(code): 237 | tab.ele(f"@data-index={i}").input(digit) 238 | time.sleep(random.uniform(0.1, 0.3)) 239 | return True 240 | 241 | time.sleep(2) 242 | 243 | except Exception as e: 244 | logging.error(f"handle_verification_code.error: {str(e)}") 245 | time.sleep(2) 246 | 247 | logging.error('email.timeout') 248 | return False 249 | 250 | 251 | class EmailGenerator: 252 | FIRST_NAMES = [ 253 | "james", "john", "robert", "michael", "william", "david", "richard", "joseph", 254 | "thomas", "charles", "christopher", "daniel", "matthew", "anthony", "donald", 255 | "emma", "olivia", "ava", "isabella", "sophia", "mia", "charlotte", "amelia", 256 | "harper", "evelyn", "abigail", "emily", "elizabeth", "sofia", "madison" 257 | ] 258 | 259 | LAST_NAMES = [ 260 | "smith", "johnson", "williams", "brown", "jones", "garcia", "miller", "davis", 261 | "rodriguez", "martinez", "hernandez", "lopez", "gonzalez", "wilson", "anderson", 262 | "thomas", "taylor", "moore", "jackson", "martin", "lee", "perez", "thompson", 263 | "white", "harris", "sanchez", "clark", "ramirez", "lewis", "robinson" 264 | ] 265 | 266 | def __init__( 267 | self, 268 | password="".join( 269 | random.choices( 270 | "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*", 271 | k=12, 272 | ) 273 | ), 274 | first_name=None, 275 | last_name=None, 276 | ): 277 | self.default_password = password 278 | self.default_first_name = first_name or random.choice(self.FIRST_NAMES) 279 | self.default_last_name = last_name or random.choice(self.LAST_NAMES) 280 | self.email = None 281 | 282 | def set_email(self, email): 283 | self.email = email 284 | 285 | def get_account_info(self): 286 | if not self.email: 287 | raise ValueError("Email address not set") 288 | return { 289 | "email": self.email, 290 | "password": self.default_password, 291 | "first_name": self.default_first_name.capitalize(), 292 | "last_name": self.default_last_name.capitalize(), 293 | } 294 | 295 | def _save_account_info(self, token, total_usage): 296 | try: 297 | file_path = os.path.join(os.getcwd(), 'cursor_accounts.txt') 298 | with open(file_path, 'a', encoding='utf-8') as f: 299 | f.write(f"\n{'='*50}\n") 300 | f.write(f"Email: {self.email}\n") 301 | f.write(f"Password: {self.default_password}\n") 302 | f.write(f"Token: {token}\n") 303 | f.write(f"Usage Limit: {total_usage}\n") 304 | f.write(f"{'='*50}\n") 305 | return True 306 | except Exception as e: 307 | logging.error(f"save_account_info.error: {str(e)}") 308 | return False 309 | 310 | def cleanup_and_exit(browser_manager=None, exit_code=0): 311 | """Clean up resources and exit program""" 312 | try: 313 | if browser_manager: 314 | logging.info("browser.quit") 315 | browser_manager.quit() 316 | 317 | current_process = psutil.Process() 318 | children = current_process.children(recursive=True) 319 | for child in children: 320 | try: 321 | child.terminate() 322 | except: 323 | pass 324 | 325 | logging.info("exit.success") 326 | sys.exit(exit_code) 327 | 328 | except Exception as e: 329 | logging.error(f"cleanup.exit.error: {str(e)}") 330 | sys.exit(1) 331 | 332 | 333 | 334 | def main(): 335 | browser_manager = None 336 | try: 337 | success, path_cursor = ExitCursor() # 在调试时可以注释掉 338 | logging.info(f'exit.cursor.success: {success}, path.cursor: {path_cursor}') 339 | 340 | browser_manager = BrowserManager() 341 | browser = browser_manager.init_browser() 342 | 343 | mail_tab = browser.new_tab(MAIL_URL) 344 | browser.activate_tab(mail_tab) 345 | time.sleep(5) 346 | 347 | email_js = get_temp_email(mail_tab) 348 | 349 | email_generator = EmailGenerator() 350 | email_generator.set_email(email_js) 351 | account_info = email_generator.get_account_info() 352 | 353 | signup_tab = browser.new_tab(SIGN_UP_URL) 354 | browser.activate_tab(signup_tab) 355 | time.sleep(2) 356 | 357 | signup_tab.run_js("try { turnstile.reset() } catch(e) { }") 358 | 359 | if sign_up_account(browser, signup_tab, account_info): 360 | token = get_cursor_session_token(signup_tab) 361 | logging.info(f'account.token: {token}') 362 | if token: 363 | email_generator._save_account_info(token, TOTAL_USAGE) 364 | update_cursor_auth( 365 | email=account_info["email"], access_token=token, refresh_token=token 366 | ) 367 | logging.info('start.machine.ids.reset') 368 | resetter = ResetterMachineIDs() 369 | resetter.reset_machine_ids() 370 | if path_cursor: 371 | try: 372 | logging.info(f"restart.cursor.path {path_cursor}") 373 | if os.name == 'nt': 374 | startupinfo = subprocess.STARTUPINFO() 375 | startupinfo.dwFlags |= subprocess.STARTF_USESHOWWINDOW 376 | subprocess.Popen([path_cursor], startupinfo=startupinfo, close_fds=True) 377 | else: 378 | subprocess.Popen(['open', path_cursor]) 379 | logging.info('restart.cursor.success') 380 | except Exception as e: 381 | logging.error(f"restart.cursor.error: {str(e)}") 382 | else: 383 | logging.error("get.cursor.session.token.failed") 384 | else: 385 | logging.error("register.failed") 386 | 387 | logging.info("register.finished") 388 | cleanup_and_exit(browser_manager, 0) 389 | 390 | except Exception as e: 391 | logging.error(f"main.error: {str(e)}") 392 | import traceback 393 | logging.error(traceback.format_exc()) 394 | cleanup_and_exit(browser_manager, 1) 395 | finally: 396 | cleanup_and_exit(browser_manager, 1) 397 | 398 | 399 | if __name__ == "__main__": 400 | main() 401 | -------------------------------------------------------------------------------- /workspace/exit_cursor.py: -------------------------------------------------------------------------------- 1 | import psutil 2 | from logger import logging 3 | import time 4 | 5 | def ExitCursor(timeout=5): 6 | try: 7 | logging.info("exit.cursor") 8 | cursor_processes = [] 9 | path_cursor = '' 10 | for proc in psutil.process_iter(['pid', 'name']): 11 | try: 12 | if proc.info['name'].lower() in ['cursor.exe', 'cursor']: 13 | try: 14 | if not path_cursor: 15 | raw_path = proc.exe() 16 | if raw_path and '.app' in raw_path: 17 | path_cursor = raw_path[:raw_path.find('.app') + 4] 18 | else: 19 | path_cursor = raw_path 20 | logging.info(f'found.cursor {path_cursor}') 21 | except (psutil.NoSuchProcess, psutil.AccessDenied): 22 | logging.warning('exit.cursor.path.error') 23 | cursor_processes.append(proc) 24 | except (psutil.NoSuchProcess, psutil.AccessDenied): 25 | continue 26 | 27 | if not cursor_processes: 28 | logging.info("not.found.cursor") 29 | return True, path_cursor 30 | 31 | for proc in cursor_processes: 32 | try: 33 | if proc.is_running(): 34 | proc.terminate() 35 | except (psutil.NoSuchProcess, psutil.AccessDenied): 36 | continue 37 | 38 | start_time = time.time() 39 | while time.time() - start_time < timeout: 40 | still_running = [] 41 | for proc in cursor_processes: 42 | try: 43 | if proc.is_running(): 44 | still_running.append(proc) 45 | except (psutil.NoSuchProcess, psutil.AccessDenied): 46 | continue 47 | 48 | if not still_running: 49 | logging.info("exit.cursor.success") 50 | return True, path_cursor 51 | 52 | time.sleep(0.5) 53 | 54 | if still_running: 55 | process_list = ", ".join([str(p.pid) for p in still_running]) 56 | logging.warning(f'exit.cursor.timeout {process_list}') 57 | return False, path_cursor 58 | 59 | return True, path_cursor 60 | 61 | except Exception as e: 62 | logging.error(f'exit.cursor.error {str(e)}') 63 | return False, '' 64 | 65 | if __name__ == "__main__": 66 | success, path = ExitCursor() 67 | if path: 68 | logging.info(f'exit.cursor.path {path}') 69 | -------------------------------------------------------------------------------- /workspace/get_email_code.py: -------------------------------------------------------------------------------- 1 | from DrissionPage.common import Keys 2 | import time 3 | import re 4 | from logger import logging 5 | 6 | 7 | class EmailVerificationHandler: 8 | def __init__(self, browser, mail_url): 9 | self.browser = browser 10 | self.mail_url = mail_url 11 | 12 | def get_verification_code(self, email): 13 | logging.info(email) 14 | code = None 15 | 16 | try: 17 | logging.info("processing.email") 18 | tab_mail = self.browser.new_tab(self.mail_url) 19 | self.browser.activate_tab(tab_mail) 20 | 21 | code = self._get_latest_mail_code(tab_mail) 22 | 23 | # self._cleanup_mail(tab_mail) 24 | 25 | tab_mail.close() 26 | 27 | except Exception as e: 28 | logging.error(f"error.getting.email.code: {str(e)}") 29 | 30 | return code 31 | 32 | def _get_latest_mail_code(self, tab): 33 | code = None 34 | retry_count = 0 35 | max_retries = 3 36 | 37 | while retry_count < max_retries: 38 | try: 39 | email_row = tab.ele("css:tbody > tr.border-b.cursor-pointer", timeout=2) 40 | if email_row: 41 | subject_cell = email_row.ele("css:td:nth-child(2)") 42 | if subject_cell and "Verify your email address" in subject_cell.text: 43 | logging.info('email.found') 44 | email_row.click() 45 | time.sleep(2) 46 | break 47 | 48 | logging.info("waiting.email.load") 49 | time.sleep(2) 50 | tab.refresh() 51 | time.sleep(3) 52 | retry_count += 1 53 | 54 | except Exception as e: 55 | logging.error(f"error.getting.email: {str(e)}") 56 | time.sleep(2) 57 | retry_count += 1 58 | 59 | if retry_count >= max_retries: 60 | logging.error("email.not.found") 61 | raise Exception("Email not found") 62 | 63 | max_retries = 10 64 | for attempt in range(max_retries): 65 | try: 66 | content_td = tab.ele("css:td.px-3.text-black.text-base", timeout=2) 67 | if content_td: 68 | content = content_td.text 69 | if content: 70 | matches = re.findall(r'\b\d{6}\b', content) 71 | for match in matches: 72 | if "verification code" in content.lower() or "verify" in content.lower(): 73 | logging.info(f"code.found: {match}") 74 | return match 75 | 76 | logging.info("waiting.code.load") 77 | time.sleep(2) 78 | 79 | except Exception as e: 80 | logging.error(f"error.getting.code: {str(e)}") 81 | time.sleep(2) 82 | 83 | logging.error("code.not.found") 84 | return None 85 | 86 | def _cleanup_mail(self, tab): 87 | if tab.ele("@id=delete_mail"): 88 | tab.actions.click("@id=delete_mail") 89 | time.sleep(1) 90 | 91 | if tab.ele("@id=confirm_mail"): 92 | tab.actions.click("@id=confirm_mail") 93 | -------------------------------------------------------------------------------- /workspace/logger.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | from datetime import datetime 4 | 5 | # Configure logging 6 | log_dir = "logs" 7 | if not os.path.exists(log_dir): 8 | os.makedirs(log_dir) 9 | 10 | logging.basicConfig( 11 | filename=os.path.join(log_dir, f"{datetime.now().strftime('%Y-%m-%d')}.log"), 12 | level=logging.DEBUG, 13 | format="%(asctime)s - %(levelname)s - %(message)s", 14 | encoding='utf-8', 15 | ) 16 | 17 | # 创建控制台处理器 18 | console_handler = logging.StreamHandler() 19 | console_handler.setLevel(logging.INFO) 20 | console_handler.setFormatter(logging.Formatter("%(message)s")) 21 | 22 | # 将控制台处理器添加到日志记录器 23 | logging.getLogger().addHandler(console_handler) 24 | 25 | def main_task(): 26 | """ 27 | Main task execution function. Simulates a workflow and handles errors. 28 | """ 29 | try: 30 | logging.info("Starting the main task...") 31 | 32 | # Simulated task and error condition 33 | if some_condition(): 34 | raise ValueError("Simulated error occurred.") 35 | 36 | logging.info("Main task completed successfully.") 37 | 38 | except ValueError as ve: 39 | logging.error(f"ValueError occurred: {ve}", exc_info=True) 40 | except Exception as e: 41 | logging.error(f"Unexpected error occurred: {e}", exc_info=True) 42 | finally: 43 | logging.info("Task execution finished.") 44 | 45 | def some_condition(): 46 | """ 47 | Simulates an error condition. Returns True to trigger an error. 48 | Replace this logic with actual task conditions. 49 | """ 50 | return True 51 | 52 | if __name__ == "__main__": 53 | # Application workflow 54 | logging.info("Application started.") 55 | main_task() 56 | logging.info("Application exited.") 57 | -------------------------------------------------------------------------------- /workspace/requirements.txt: -------------------------------------------------------------------------------- 1 | DrissionPage==4.1.0.9 2 | psutil==6.1.0 -------------------------------------------------------------------------------- /workspace/turnstilePatch/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "Turnstile Patcher", 4 | "version": "2.1", 5 | "content_scripts": [ 6 | { 7 | "js": [ 8 | "./script.js" 9 | ], 10 | "matches": [ 11 | "" 12 | ], 13 | "run_at": "document_start", 14 | "all_frames": true, 15 | "world": "MAIN" 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /workspace/turnstilePatch/readme.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /workspace/turnstilePatch/script.js: -------------------------------------------------------------------------------- 1 | function getRandomInt(min, max) { 2 | return Math.floor(Math.random() * (max - min + 1)) + min; 3 | } 4 | 5 | // old method wouldn't work on 4k screens 6 | 7 | let screenX = getRandomInt(800, 1200); 8 | let screenY = getRandomInt(400, 600); 9 | 10 | Object.defineProperty(MouseEvent.prototype, 'screenX', { value: screenX }); 11 | 12 | Object.defineProperty(MouseEvent.prototype, 'screenY', { value: screenY }); --------------------------------------------------------------------------------