Full Code of mihomo-party-org/clash-party for AI

smart_core e2fbb2a8ad9b cached
216 files
3.4 MB
894.9k tokens
516 symbols
1 requests
Download .txt
Showing preview only (3,575K chars total). Download the full file or copy to clipboard to get everything.
Repository: mihomo-party-org/clash-party
Branch: smart_core
Commit: e2fbb2a8ad9b
Files: 216
Total size: 3.4 MB

Directory structure:
gitextract_lnqrk73k/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report_zh.yml
│   │   ├── config.yml
│   │   └── feature_request_zh.yml
│   └── workflows/
│       ├── build.yml
│       └── issues.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc.yaml
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── LICENSE
├── README.md
├── aur/
│   ├── mihomo-party/
│   │   ├── PKGBUILD
│   │   ├── mihomo-party.install
│   │   └── mihomo-party.sh
│   ├── mihomo-party-bin/
│   │   ├── PKGBUILD
│   │   ├── mihomo-party.install
│   │   └── mihomo-party.sh
│   ├── mihomo-party-electron/
│   │   ├── PKGBUILD
│   │   ├── mihomo-party.desktop
│   │   ├── mihomo-party.install
│   │   └── mihomo-party.sh
│   ├── mihomo-party-electron-bin/
│   │   ├── PKGBUILD
│   │   ├── mihomo-party.desktop
│   │   ├── mihomo-party.install
│   │   └── mihomo-party.sh
│   └── mihomo-party-git/
│       ├── PKGBUILD
│       ├── mihomo-party.install
│       └── mihomo-party.sh
├── build/
│   ├── entitlements.mac.plist
│   ├── icon.icns
│   ├── linux/
│   │   ├── postinst
│   │   └── postuninst
│   └── pkg-scripts/
│       ├── postinstall
│       └── preinstall
├── changelog.md
├── electron-builder.yml
├── electron.vite.config.ts
├── eslint.config.cjs
├── package.json
├── scripts/
│   ├── checksum.mjs
│   ├── cleanup-mac.sh
│   ├── copy-legacy-artifacts.mjs
│   ├── prepare.mjs
│   ├── telegram.mjs
│   ├── update-version.mjs
│   ├── updater.mjs
│   └── version-utils.mjs
├── src/
│   ├── main/
│   │   ├── config/
│   │   │   ├── app.ts
│   │   │   ├── controledMihomo.ts
│   │   │   ├── index.ts
│   │   │   ├── override.ts
│   │   │   ├── profile.ts
│   │   │   └── smartOverride.ts
│   │   ├── core/
│   │   │   ├── dns.ts
│   │   │   ├── factory.ts
│   │   │   ├── manager.ts
│   │   │   ├── mihomoApi.ts
│   │   │   ├── permissions.ts
│   │   │   ├── process.ts
│   │   │   ├── profileUpdater.ts
│   │   │   └── subStoreApi.ts
│   │   ├── deeplink.ts
│   │   ├── index.ts
│   │   ├── lifecycle.ts
│   │   ├── resolve/
│   │   │   ├── autoUpdater.ts
│   │   │   ├── backup.ts
│   │   │   ├── floatingWindow.ts
│   │   │   ├── gistApi.ts
│   │   │   ├── server.ts
│   │   │   ├── shortcut.ts
│   │   │   ├── theme.ts
│   │   │   ├── trafficMonitor.ts
│   │   │   └── tray.ts
│   │   ├── sys/
│   │   │   ├── autoRun.ts
│   │   │   ├── interface.ts
│   │   │   ├── misc.ts
│   │   │   ├── ssid.ts
│   │   │   └── sysproxy.ts
│   │   ├── utils/
│   │   │   ├── appName.ts
│   │   │   ├── calc.ts
│   │   │   ├── chromeRequest.ts
│   │   │   ├── defaultIcon.ts
│   │   │   ├── dirs.ts
│   │   │   ├── github.ts
│   │   │   ├── icon.ts
│   │   │   ├── image.ts
│   │   │   ├── init.ts
│   │   │   ├── ipc.ts
│   │   │   ├── logger.ts
│   │   │   ├── merge.ts
│   │   │   ├── template.ts
│   │   │   └── yaml.ts
│   │   └── window.ts
│   ├── native/
│   │   └── sysproxy/
│   │       ├── index.d.ts
│   │       ├── index.js
│   │       └── package.json
│   ├── preload/
│   │   ├── index.d.ts
│   │   └── index.ts
│   ├── renderer/
│   │   ├── floating.html
│   │   ├── index.html
│   │   └── src/
│   │       ├── App.tsx
│   │       ├── FloatingApp.tsx
│   │       ├── assets/
│   │       │   ├── floating.css
│   │       │   ├── hero.ts
│   │       │   └── main.css
│   │       ├── components/
│   │       │   ├── base/
│   │       │   │   ├── base-confirm-modal.tsx
│   │       │   │   ├── base-editor.tsx
│   │       │   │   ├── base-error-boundary.tsx
│   │       │   │   ├── base-page.tsx
│   │       │   │   ├── base-setting-card.tsx
│   │       │   │   ├── base-setting-item.tsx
│   │       │   │   ├── border-switch.css
│   │       │   │   ├── border-swtich.tsx
│   │       │   │   ├── collapse-input.tsx
│   │       │   │   ├── mihomo-icon.tsx
│   │       │   │   ├── substore-icon.tsx
│   │       │   │   └── toast.tsx
│   │       │   ├── connections/
│   │       │   │   ├── connection-detail-modal.tsx
│   │       │   │   ├── connection-item.tsx
│   │       │   │   └── connection-table.tsx
│   │       │   ├── logs/
│   │       │   │   └── log-item.tsx
│   │       │   ├── mihomo/
│   │       │   │   └── interface-modal.tsx
│   │       │   ├── override/
│   │       │   │   ├── edit-file-modal.tsx
│   │       │   │   ├── edit-info-modal.tsx
│   │       │   │   ├── exec-log-modal.tsx
│   │       │   │   └── override-item.tsx
│   │       │   ├── profiles/
│   │       │   │   ├── edit-file-modal.tsx
│   │       │   │   ├── edit-info-modal.tsx
│   │       │   │   ├── edit-rules-modal.tsx
│   │       │   │   └── profile-item.tsx
│   │       │   ├── proxies/
│   │       │   │   └── proxy-item.tsx
│   │       │   ├── resources/
│   │       │   │   ├── geo-data.tsx
│   │       │   │   ├── proxy-provider.tsx
│   │       │   │   ├── rule-provider.tsx
│   │       │   │   └── viewer.tsx
│   │       │   ├── rules/
│   │       │   │   └── rule-item.tsx
│   │       │   ├── settings/
│   │       │   │   ├── actions.tsx
│   │       │   │   ├── css-editor-modal.tsx
│   │       │   │   ├── general-config.tsx
│   │       │   │   ├── local-backup-config.tsx
│   │       │   │   ├── mihomo-config.tsx
│   │       │   │   ├── shortcut-config.tsx
│   │       │   │   ├── sider-config.tsx
│   │       │   │   ├── substore-config.tsx
│   │       │   │   ├── webdav-config.tsx
│   │       │   │   └── webdav-restore-modal.tsx
│   │       │   ├── sider/
│   │       │   │   ├── config-viewer.tsx
│   │       │   │   ├── conn-card.tsx
│   │       │   │   ├── dns-card.tsx
│   │       │   │   ├── log-card.tsx
│   │       │   │   ├── mihomo-core-card.tsx
│   │       │   │   ├── outbound-mode-switcher.tsx
│   │       │   │   ├── override-card.tsx
│   │       │   │   ├── profile-card.tsx
│   │       │   │   ├── proxy-card.tsx
│   │       │   │   ├── resource-card.tsx
│   │       │   │   ├── rule-card.tsx
│   │       │   │   ├── sniff-card.tsx
│   │       │   │   ├── substore-card.tsx
│   │       │   │   ├── sysproxy-switcher.tsx
│   │       │   │   └── tun-switcher.tsx
│   │       │   ├── sysproxy/
│   │       │   │   └── pac-editor-modal.tsx
│   │       │   └── updater/
│   │       │       ├── updater-button.tsx
│   │       │       └── updater-modal.tsx
│   │       ├── floating.tsx
│   │       ├── hooks/
│   │       │   ├── create-config-context.tsx
│   │       │   ├── use-app-config.tsx
│   │       │   ├── use-controled-mihomo-config.tsx
│   │       │   ├── use-groups.tsx
│   │       │   ├── use-override-config.tsx
│   │       │   ├── use-profile-config.tsx
│   │       │   └── use-rules.tsx
│   │       ├── i18n.ts
│   │       ├── locales/
│   │       │   ├── en-US.json
│   │       │   ├── fa-IR.json
│   │       │   ├── ru-RU.json
│   │       │   ├── zh-CN.json
│   │       │   └── zh-TW.json
│   │       ├── main.tsx
│   │       ├── pages/
│   │       │   ├── connections.tsx
│   │       │   ├── dns.tsx
│   │       │   ├── logs.tsx
│   │       │   ├── mihomo.tsx
│   │       │   ├── override.tsx
│   │       │   ├── profiles.tsx
│   │       │   ├── proxies.tsx
│   │       │   ├── resources.tsx
│   │       │   ├── rules.tsx
│   │       │   ├── settings.tsx
│   │       │   ├── sniffer.tsx
│   │       │   ├── substore.tsx
│   │       │   ├── sysproxy.tsx
│   │       │   └── tun.tsx
│   │       ├── routes/
│   │       │   └── index.tsx
│   │       └── utils/
│   │           ├── calc.ts
│   │           ├── dayjs.ts
│   │           ├── debounce.ts
│   │           ├── env.d.ts
│   │           ├── error-display.ts
│   │           ├── hash.ts
│   │           ├── icon-cache.ts
│   │           ├── image.ts
│   │           ├── includes.ts
│   │           ├── init.ts
│   │           ├── ipc.ts
│   │           ├── tour.ts
│   │           └── validate.ts
│   └── shared/
│       ├── i18n.ts
│       └── types.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── tsconfig.web.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .editorconfig
================================================
root = true

[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

================================================
FILE: .github/ISSUE_TEMPLATE/bug_report_zh.yml
================================================
name: 错误反馈
description: '提交 clash-party 漏洞'
title: '[Bug] '
body:
  - type: checkboxes
    id: ensure
    attributes:
      label: Verify steps
      description: 在提交之前,请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
      options:
        - label: 我已在标题简短的描述了我所遇到的问题
        - label: 我已在 [Issue Tracker](./?q=is%3Aissue) 中寻找过我要提出的问题,但未找到相同的问题
        - label: 我已在 [常见问题](https://clashparty.org/docs/issues/common) 中寻找过我要提出的问题,并没有找到答案
        - label: 这是 GUI 程序的问题,而不是内核程序的问题
        - label: 我已经关闭所有杀毒软件/代理软件后测试过,问题依旧存在
        - label: 我已经使用最新的测试版本测试过,问题依旧存在

  - type: dropdown
    attributes:
      label: 操作系统
      description: 请提供操作系统类型
      multiple: true
      options:
        - MacOS
        - Windows
        - Linux
    validations:
      required: true
  - type: input
    attributes:
      label: 系统版本
      description: 请提供出现问题的操作系统版本
    validations:
      required: true
  - type: input
    attributes:
      label: 发生问题 clash-party 版本
    validations:
      required: true
  - type: textarea
    attributes:
      label: 描述
      description: 请提供错误的详细描述。
    validations:
      required: true
  - type: textarea
    attributes:
      label: 重现方式
      description: 请提供重现错误的步骤
    validations:
      required: true


================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
  - name: '常见问题'
    about: '提出问题前请先查看常见问题'
    url: 'https://clashparty.org/docs/issues/common'
  - name: '交流群组'
    about: '提问/讨论性质的问题请勿提交issue'
    url: 'https://t.me/mihomo_party_group'


================================================
FILE: .github/ISSUE_TEMPLATE/feature_request_zh.yml
================================================
name: 功能请求
description: '请求 clash-party 功能'
title: '[Feature] '
body:
  - type: checkboxes
    id: ensure
    attributes:
      label: Verify steps
      description: 在提交之前,请勾选以下所有选项以证明您已经阅读并理解了以下要求,否则该 issue 将被关闭。
      options:
        - label: 我已在标题简短的描述了我所需的功能
        - label: 我已在 [Issue Tracker](./?q=is%3Aissue) 中寻找过,但未找到我所需的功能
        - label: 这是向 GUI 程序提出的功能请求,而不是内核程序
        - label: 我未在最新的测试版本找到我所需的功能

  - type: dropdown
    attributes:
      label: 操作系统
      description: 请提供操作系统类型
      multiple: true
      options:
        - MacOS
        - Windows
        - Linux
    validations:
      required: true
  - type: textarea
    attributes:
      label: 描述
      description: 请提供所需功能的详细描述
    validations:
      required: true


================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
  push:
    tags:
      - v*
    paths-ignore:
      - 'README.md'
      - '.github/ISSUE_TEMPLATE/**'
      - '.github/workflows/issues.yml'
  workflow_dispatch:

permissions: write-all

jobs:
  cleanup-dev-release:
    runs-on: ubuntu-latest
    steps:
      - name: Delete Dev Release Assets
        if: github.event_name == 'workflow_dispatch'
        continue-on-error: true
        run: |
          # Get release ID for dev tag
          echo "🔍 Looking for existing dev release..."
          RELEASE_ID=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
            "https://api.github.com/repos/${{ github.repository }}/releases/tags/dev" | \
            jq -r '.id // empty')

          if [ ! -z "$RELEASE_ID" ] && [ "$RELEASE_ID" != "empty" ]; then
            echo "✅ Found dev release with ID: $RELEASE_ID"

            echo "📋 Getting list of assets with pagination..."
            ALL_ASSETS="[]"
            PAGE=1
            PER_PAGE=100
            
            while true; do
              echo "📄 Fetching page $PAGE..."
              ASSETS_PAGE=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
                "https://api.github.com/repos/${{ github.repository }}/releases/$RELEASE_ID/assets?page=$PAGE&per_page=$PER_PAGE")

              PAGE_COUNT=$(echo "$ASSETS_PAGE" | jq '. | length')
              echo "📦 Found $PAGE_COUNT assets on page $PAGE"
              
              if [ "$PAGE_COUNT" -eq 0 ]; then
                echo "📋 No more assets found, stopping pagination"
                break
              fi

              ALL_ASSETS=$(echo "$ALL_ASSETS" "$ASSETS_PAGE" | jq -s '.[0] + .[1]')

              if [ "$PAGE_COUNT" -lt "$PER_PAGE" ]; then
                echo "📋 Last page reached (got $PAGE_COUNT < $PER_PAGE), stopping pagination"
                break
              fi
              
              PAGE=$((PAGE + 1))
            done
            
            TOTAL_ASSET_COUNT=$(echo "$ALL_ASSETS" | jq '. | length')
            echo "📦 Total assets found across all pages: $TOTAL_ASSET_COUNT"
            
            if [ "$TOTAL_ASSET_COUNT" -gt 0 ]; then
              # Delete each asset with detailed logging
              echo "$ALL_ASSETS" | jq -r '.[].id' | while read asset_id; do
                if [ ! -z "$asset_id" ]; then
                  echo "🗑️ Deleting asset ID: $asset_id"
                  RESPONSE=$(curl -s -w "HTTPSTATUS:%{http_code}" -X DELETE \
                    -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
                    "https://api.github.com/repos/${{ github.repository }}/releases/assets/$asset_id")
                  
                  HTTP_CODE=$(echo $RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://')
                  if [ "$HTTP_CODE" = "204" ]; then
                    echo "✅ Successfully deleted asset $asset_id"
                  else
                    echo "❌ Failed to delete asset $asset_id (HTTP: $HTTP_CODE)"
                    echo "Response: $(echo $RESPONSE | sed -e 's/HTTPSTATUS:.*//')"
                  fi
                  # Add small delay to avoid rate limiting
                  sleep 0.5
                fi
              done
              echo "🎉 Finished deleting all $TOTAL_ASSET_COUNT assets"
            else
              echo "ℹ️  No assets found to delete"
            fi
          else
            echo "ℹ️  No existing dev release found"
          fi
      - name: Skip for Tag Release
        if: startsWith(github.ref, 'refs/tags/v')
        run: echo "Skipping cleanup for tag release"
  windows:
    needs: [cleanup-dev-release]
    if: always() && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch')
    strategy:
      fail-fast: false
      matrix:
        arch:
          - x64
          - ia32
          - arm64
    runs-on: windows-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: 'pnpm'
      - name: Install Dependencies
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
        run: pnpm install
      - name: Update Version for Dev Build
        if: github.event_name == 'workflow_dispatch'
        env:
          GITHUB_EVENT_NAME: workflow_dispatch
        run: node scripts/update-version.mjs
      - name: Prepare
        run: pnpm prepare --${{ matrix.arch }}
      - name: Build
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
        run: pnpm build:win --${{ matrix.arch }}
      - name: Add Portable Flag
        run: |
          New-Item -Path "PORTABLE" -ItemType File
          Get-ChildItem dist/*portable.7z | ForEach-Object {
            7z a $_.FullName PORTABLE
          }
      - name: Generate checksums
        run: pnpm checksum setup.exe portable.7z
      - name: Copy Legacy Artifacts
        run: pnpm copy-legacy
      - name: Upload Artifacts
        if: startsWith(github.ref, 'refs/heads/')
        uses: actions/upload-artifact@v6
        with:
          name: Windows ${{ matrix.arch }}
          path: |
            dist/*.sha256
            dist/*setup.exe
            dist/*portable.7z
          if-no-files-found: error
      - name: Publish Release
        if: startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v2
        with:
          files: |
            dist/*.sha256
            dist/*setup.exe
            dist/*portable.7z
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish Dev Release
        if: github.event_name == 'workflow_dispatch'
        uses: softprops/action-gh-release@v2
        with:
          tag_name: dev
          files: |
            dist/*.sha256
            dist/*setup.exe
            dist/*portable.7z
          prerelease: true
          draft: false
          token: ${{ secrets.GITHUB_TOKEN }}

  windows7:
    needs: [cleanup-dev-release]
    if: always() && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch')
    strategy:
      fail-fast: false
      matrix:
        arch:
          - x64
          - ia32
    runs-on: windows-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: 'pnpm'
      - name: Install Dependencies
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
        run: |
          pnpm install
          pnpm add -D electron@22.3.27
          (Get-Content electron-builder.yml) -replace 'windows', 'win7' | Set-Content electron-builder.yml
          # Electron 22 requires CJS format
          (Get-Content package.json) -replace '"type": "module"', '"type": "commonjs"' | Set-Content package.json
      - name: Update Version for Dev Build
        if: github.event_name == 'workflow_dispatch'
        env:
          GITHUB_EVENT_NAME: workflow_dispatch
        run: node scripts/update-version.mjs
      - name: Prepare
        env:
          LEGACY_BUILD: 'true'
        run: pnpm prepare --${{ matrix.arch }}
      - name: Build
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
          LEGACY_BUILD: 'true'
        run: pnpm build:win --${{ matrix.arch }}
      - name: Add Portable Flag
        run: |
          New-Item -Path "PORTABLE" -ItemType File
          Get-ChildItem dist/*portable.7z | ForEach-Object {
            7z a $_.FullName PORTABLE
          }
      - name: Generate checksums
        run: pnpm checksum setup.exe portable.7z
      - name: Copy Legacy Artifacts
        run: pnpm copy-legacy
      - name: Upload Artifacts
        if: startsWith(github.ref, 'refs/heads/')
        uses: actions/upload-artifact@v6
        with:
          name: Win7 ${{ matrix.arch }}
          path: |
            dist/*.sha256
            dist/*setup.exe
            dist/*portable.7z
          if-no-files-found: error
      - name: Publish Release
        if: startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v2
        with:
          files: |
            dist/*.sha256
            dist/*setup.exe
            dist/*portable.7z
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish Dev Release
        if: github.event_name == 'workflow_dispatch'
        uses: softprops/action-gh-release@v2
        with:
          tag_name: dev
          files: |
            dist/*.sha256
            dist/*setup.exe
            dist/*portable.7z
          prerelease: true
          draft: false
          token: ${{ secrets.GITHUB_TOKEN }}

  linux:
    needs: [cleanup-dev-release]
    if: always() && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch')
    strategy:
      fail-fast: false
      matrix:
        arch:
          - x64
          - arm64
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: 'pnpm'
      - name: Install Dependencies
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
        run: |
          pnpm install
          sed -i "s/productName: Clash Party/productName: clash-party/" electron-builder.yml
      - name: Update Version for Dev Build
        if: github.event_name == 'workflow_dispatch'
        env:
          GITHUB_EVENT_NAME: workflow_dispatch
        run: node scripts/update-version.mjs
      - name: Prepare
        run: pnpm prepare --${{ matrix.arch }}
      - name: Build
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
        run: pnpm build:linux --${{ matrix.arch }}
      - name: Generate checksums
        run: pnpm checksum .deb .rpm
      - name: Copy Legacy Artifacts
        run: pnpm copy-legacy
      - name: Upload Artifacts
        if: startsWith(github.ref, 'refs/heads/')
        uses: actions/upload-artifact@v6
        with:
          name: Linux ${{ matrix.arch }}
          path: |
            dist/*.sha256
            dist/*.deb
            dist/*.rpm
          if-no-files-found: error
      - name: Publish Release
        if: startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v2
        with:
          files: |
            dist/*.sha256
            dist/*.deb
            dist/*.rpm
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish Dev Release
        if: github.event_name == 'workflow_dispatch'
        uses: softprops/action-gh-release@v2
        with:
          tag_name: dev
          files: |
            dist/*.sha256
            dist/*.deb
            dist/*.rpm
          prerelease: true
          draft: false
          token: ${{ secrets.GITHUB_TOKEN }}

  macos:
    needs: [cleanup-dev-release]
    if: always() && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch')
    strategy:
      fail-fast: false
      matrix:
        arch:
          - x64
          - arm64
    runs-on: macos-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: 'pnpm'
      - name: Install Dependencies
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
        run: pnpm install
      - name: Update Version for Dev Build
        if: github.event_name == 'workflow_dispatch'
        env:
          GITHUB_EVENT_NAME: workflow_dispatch
        run: node scripts/update-version.mjs
      - name: Prepare
        run: pnpm prepare --${{ matrix.arch }}
      - name: Build
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          CSC_LINK: ${{ secrets.CSC_LINK }}
          CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
        run: |
          chmod +x build/pkg-scripts/postinstall build/pkg-scripts/preinstall
          pnpm build:mac --${{ matrix.arch }}
      - name: Setup temporary installer signing keychain
        uses: apple-actions/import-codesign-certs@v6
        with:
          p12-file-base64: ${{ secrets.CSC_INSTALLER_LINK }}
          p12-password: ${{ secrets.CSC_INSTALLER_KEY_PASSWORD }}
      - name: Sign the Apple pkg
        run: |
          for pkg_name in $(ls -1 dist/*.pkg); do
            pkg_name=$(ls -1 dist/*.pkg)
            mv $pkg_name Unsigned-Workbench.pkg
            productsign --sign "Developer ID Installer: Prometheus Advertising Corp (489PDK5LP3)" Unsigned-Workbench.pkg $pkg_name
            rm -f Unsigned-Workbench.pkg
            xcrun notarytool submit $pkg_name --apple-id $APPLE_ID --team-id $APPLE_TEAM_ID --password $APPLE_APP_SPECIFIC_PASSWORD --wait
          done
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
      - name: Generate checksums
        run: pnpm checksum .pkg
      - name: Copy Legacy Artifacts
        run: pnpm copy-legacy
      - name: Upload Artifacts
        if: startsWith(github.ref, 'refs/heads/')
        uses: actions/upload-artifact@v6
        with:
          name: MacOS ${{ matrix.arch }}
          path: |
            dist/*.sha256
            dist/*.pkg
          if-no-files-found: error
      - name: Publish Release
        if: startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v2
        with:
          files: |
            dist/*.sha256
            dist/*.pkg
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish Dev Release
        if: github.event_name == 'workflow_dispatch'
        uses: softprops/action-gh-release@v2
        with:
          tag_name: dev
          files: |
            dist/*.sha256
            dist/*.pkg
          prerelease: true
          draft: false
          token: ${{ secrets.GITHUB_TOKEN }}

  macos10:
    needs: [cleanup-dev-release]
    if: always() && (startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch')
    strategy:
      fail-fast: false
      matrix:
        arch:
          - x64
          - arm64
    runs-on: macos-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: 'pnpm'
      - name: Install Dependencies
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
        run: |
          pnpm install
          pnpm add -D electron@32.2.2
      - name: Update Version for Dev Build
        if: github.event_name == 'workflow_dispatch'
        env:
          GITHUB_EVENT_NAME: workflow_dispatch
        run: node scripts/update-version.mjs
      - name: Prepare
        run: pnpm prepare --${{ matrix.arch }}
      - name: Build
        env:
          npm_config_arch: ${{ matrix.arch }}
          npm_config_target_arch: ${{ matrix.arch }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
          CSC_LINK: ${{ secrets.CSC_LINK }}
          CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
        run: |
          sed -i "" -e "s/macos/catalina/" electron-builder.yml
          chmod +x build/pkg-scripts/postinstall build/pkg-scripts/preinstall
          pnpm build:mac --${{ matrix.arch }}
      - name: Setup temporary installer signing keychain
        uses: apple-actions/import-codesign-certs@v6
        with:
          p12-file-base64: ${{ secrets.CSC_INSTALLER_LINK }}
          p12-password: ${{ secrets.CSC_INSTALLER_KEY_PASSWORD }}
      - name: Sign the Apple pkg
        run: |
          for pkg_name in $(ls -1 dist/*.pkg); do
            pkg_name=$(ls -1 dist/*.pkg)
            mv $pkg_name Unsigned-Workbench.pkg
            productsign --sign "Developer ID Installer: Prometheus Advertising Corp (489PDK5LP3)" Unsigned-Workbench.pkg $pkg_name
            rm -f Unsigned-Workbench.pkg
            xcrun notarytool submit $pkg_name --apple-id $APPLE_ID --team-id $APPLE_TEAM_ID --password $APPLE_APP_SPECIFIC_PASSWORD --wait
          done
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
      - name: Generate checksums
        run: pnpm checksum .pkg
      - name: Copy Legacy Artifacts
        run: pnpm copy-legacy
      - name: Upload Artifacts
        if: startsWith(github.ref, 'refs/heads/')
        uses: actions/upload-artifact@v6
        with:
          name: Catalina ${{ matrix.arch }}
          path: |
            dist/*.sha256
            dist/*.pkg
          if-no-files-found: error
      - name: Publish Release
        if: startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v2
        with:
          files: |
            dist/*.sha256
            dist/*.pkg
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish Dev Release
        if: github.event_name == 'workflow_dispatch'
        uses: softprops/action-gh-release@v2
        with:
          tag_name: dev
          files: |
            dist/*.sha256
            dist/*.pkg
          prerelease: true
          draft: false
          token: ${{ secrets.GITHUB_TOKEN }}

  updater:
    if: startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch'
    needs: [windows, windows7, linux, macos, macos10]
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node.js
        uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: 'pnpm'
      - name: Install Dependencies
        run: pnpm install
      - name: Update Version for Dev Build
        if: github.event_name == 'workflow_dispatch'
        env:
          GITHUB_EVENT_NAME: workflow_dispatch
        run: node scripts/update-version.mjs
      - name: Telegram Notification
        if: startsWith(github.ref, 'refs/tags/v')
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          RELEASE_TYPE: release
        run: pnpm telegram
      - name: Telegram Dev Notification
        if: github.event_name == 'workflow_dispatch'
        env:
          TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
          GITHUB_SHA: ${{ github.sha }}
          RELEASE_TYPE: dev
        run: pnpm telegram:dev
      - name: Generate latest.yml
        run: pnpm updater
      - name: Publish Release
        if: startsWith(github.ref, 'refs/tags/v')
        uses: softprops/action-gh-release@v2
        with:
          files: latest.yml
          body_path: changelog.md
          token: ${{ secrets.GITHUB_TOKEN }}
      - name: Publish Dev Release
        if: github.event_name == 'workflow_dispatch'
        uses: softprops/action-gh-release@v2
        with:
          tag_name: dev
          files: latest.yml
          body_path: changelog.md
          prerelease: true
          draft: false
          token: ${{ secrets.GITHUB_TOKEN }}

  aur-release-updater:
    strategy:
      fail-fast: false
      matrix:
        pkgname:
          - mihomo-party-electron-bin
          - mihomo-party-electron
          - mihomo-party-bin
          - mihomo-party
    if: startsWith(github.ref, 'refs/tags/v')
    needs: updater
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Update Version
        run: |
          sed -i "s/pkgver=.*/pkgver=$(echo ${{ github.ref }} | tr -d 'refs/tags/v')/" aur/${{ matrix.pkgname }}/PKGBUILD
      - name: Update Checksums
        if: matrix.pkgname == 'mihomo-party' || matrix.pkgname == 'mihomo-party-electron'
        run: |
          wget https://github.com/${{ github.repository }}/archive/refs/tags/$(echo ${{ github.ref }} | tr -d 'refs/tags/').tar.gz -O release.tar.gz
          sed -i "s/sha256sums=.*/sha256sums=(\"$(sha256sum ./release.tar.gz | awk '{print $1}')\"/" aur/mihomo-party/PKGBUILD
          sed -i "s/sha256sums=.*/sha256sums=(\"$(sha256sum ./release.tar.gz | awk '{print $1}')\"/" aur/mihomo-party-electron/PKGBUILD
      - name: Update Checksums
        if: matrix.pkgname == 'mihomo-party-bin' || matrix.pkgname == 'mihomo-party-electron-bin'
        run: |
          wget https://github.com/${{ github.repository }}/releases/download/$(echo ${{ github.ref }} | tr -d 'refs/tags/')/clash-party-linux-$(echo ${{ github.ref }} | tr -d 'refs/tags/v')-amd64.deb -O amd64.deb
          wget https://github.com/${{ github.repository }}/releases/download/$(echo ${{ github.ref }} | tr -d 'refs/tags/')/clash-party-linux-$(echo ${{ github.ref }} | tr -d 'refs/tags/v')-arm64.deb -O arm64.deb
          sed -i "s/sha256sums_x86_64=.*/sha256sums_x86_64=(\"$(sha256sum ./amd64.deb | awk '{print $1}')\")/" aur/mihomo-party-bin/PKGBUILD
          sed -i "s/sha256sums_aarch64=.*/sha256sums_aarch64=(\"$(sha256sum ./arm64.deb | awk '{print $1}')\")/" aur/mihomo-party-bin/PKGBUILD
          sed -i "s/sha256sums_x86_64=.*/sha256sums_x86_64=(\"$(sha256sum ./amd64.deb | awk '{print $1}')\")/" aur/mihomo-party-electron-bin/PKGBUILD
          sed -i "s/sha256sums_aarch64=.*/sha256sums_aarch64=(\"$(sha256sum ./arm64.deb | awk '{print $1}')\")/" aur/mihomo-party-electron-bin/PKGBUILD
      - name: Publish AUR package
        uses: KSXGitHub/github-actions-deploy-aur@v2.7.2
        with:
          pkgname: ${{ matrix.pkgname }}
          pkgbuild: aur/${{ matrix.pkgname }}/PKGBUILD
          commit_username: pompurin404
          commit_email: pompurin404@mihomo.party
          ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
          commit_message: Update AUR package
          ssh_keyscan_types: rsa,ed25519
          allow_empty_commits: false

  aur-git-updater:
    if: startsWith(github.ref, 'refs/heads/')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 0
      - name: update version
        run: |
          sed -i "s/pkgver=.*/pkgver=$(git describe --long 2>/dev/null | sed 's/\([^-]*-g\)/r\1/;s/-/./g' | tr -d 'v')/" aur/mihomo-party-git/PKGBUILD
      - name: Publish AUR package
        uses: KSXGitHub/github-actions-deploy-aur@v2.7.2
        with:
          pkgname: mihomo-party-git
          pkgbuild: aur/mihomo-party-git/PKGBUILD
          commit_username: pompurin404
          commit_email: pompurin404@mihomo.party
          ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
          commit_message: Update AUR package
          ssh_keyscan_types: rsa,ed25519
          allow_empty_commits: false

  winget:
    if: startsWith(github.ref, 'refs/tags/v')
    name: Update WinGet Package
    needs: windows
    runs-on: ubuntu-latest
    steps:
      - name: Get Tag Name
        run: echo "VERSION=$(echo ${{ github.ref }} | tr -d 'refs/tags/v')" >> $GITHUB_ENV
      - name: Submit to Winget
        uses: vedantmgoyal9/winget-releaser@main
        with:
          identifier: Mihomo-Party.Mihomo-Party
          version: ${{env.VERSION}}
          release-tag: v${{env.VERSION}}
          installers-regex: 'clash-party-windows-.*setup\.exe$'
          token: ${{ secrets.POMPURIN404_TOKEN }}


================================================
FILE: .github/workflows/issues.yml
================================================
name: Review Issues

on:
  issues:
    types: [opened]

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - name: Generate Token
        uses: tibdex/github-app-token@v2
        id: generate
        with:
          app_id: ${{ secrets.BOT_APP_ID }}
          private_key: ${{ secrets.BOT_PRIVATE_KEY }}
      - name: Review Issues
        uses: mihomo-party-org/universal-assistant@v1.0.3
        with:
          github_token: ${{ steps.generate.outputs.token }}
          openai_base_url: ${{ secrets.OPENAI_BASE_URL }}
          openai_api_key: ${{ secrets.OPENAI_API_KEY }}
          openai_model: ${{ vars.OPENAI_MODEL }}
          system_prompt: ${{ vars.SYSTEM_PROMPT }}
          available_tools: ${{ vars.AVAILABLE_TOOLS }}
          user_input: |
            请审查如下 Issue:
            标题:"${{ github.event.issue.title }}"
            内容:"${{ github.event.issue.body }}"


================================================
FILE: .gitignore
================================================
node_modules
resources/files
resources/sidecar
extra
dist
out
.DS_Store
*.log*
.idea
*.ttf
party.md
CLAUDE.md
tsconfig.node.tsbuildinfo


================================================
FILE: .npmrc
================================================
shamefully-hoist=true
virtual-store-dir-max-length=80
public-hoist-pattern[]=*@heroui/*
only-built-dependencies[]=electron
only-built-dependencies[]=esbuild
only-built-dependencies[]=meta-json-schema


================================================
FILE: .prettierignore
================================================
out
dist
pnpm-lock.yaml
LICENSE.md
tsconfig.json
tsconfig.*.json


================================================
FILE: .prettierrc.yaml
================================================
singleQuote: true
semi: false
printWidth: 100
trailingComma: none


================================================
FILE: .vscode/extensions.json
================================================
{
  "recommendations": [
    "dbaeumer.vscode-eslint",
    "esbenp.prettier-vscode",
    "bradlc.vscode-tailwindcss"
  ]
}


================================================
FILE: .vscode/launch.json
================================================
{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Debug Main Process",
      "type": "node",
      "request": "launch",
      "cwd": "${workspaceRoot}",
      "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
      "windows": {
        "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
      },
      "runtimeArgs": ["--sourcemap"],
      "env": {
        "REMOTE_DEBUGGING_PORT": "9222"
      }
    },
    {
      "name": "Debug Renderer Process",
      "port": 9222,
      "request": "attach",
      "type": "chrome",
      "webRoot": "${workspaceFolder}/src/renderer",
      "timeout": 60000,
      "presentation": {
        "hidden": true
      }
    }
  ],
  "compounds": [
    {
      "name": "Debug All",
      "configurations": ["Debug Main Process", "Debug Renderer Process"],
      "presentation": {
        "order": 1
      }
    }
  ]
}


================================================
FILE: .vscode/settings.json
================================================
{
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "terminal.integrated.defaultProfile.windows": "PowerShell"
}


================================================
FILE: LICENSE
================================================
                    GNU GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.

                            Preamble

  The GNU General Public License is a free, copyleft license for
software and other kinds of works.

  The licenses for most software and other practical works are designed
to take away your freedom to share and change the works.  By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users.  We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors.  You can apply it to
your programs, too.

  When we speak of free software, we are referring to freedom, not
price.  Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.

  To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights.  Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.

  For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received.  You must make sure that they, too, receive
or can get the source code.  And you must show them these terms so they
know their rights.

  Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.

  For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software.  For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.

  Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so.  This is fundamentally incompatible with the aim of
protecting users' freedom to change the software.  The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable.  Therefore, we
have designed this version of the GPL to prohibit the practice for those
products.  If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.

  Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary.  To prevent this, the GPL assures that
patents cannot be used to render the program non-free.

  The precise terms and conditions for copying, distribution and
modification follow.

                       TERMS AND CONDITIONS

  0. Definitions.

  "This License" refers to version 3 of the GNU General Public License.

  "Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.

  "The Program" refers to any copyrightable work licensed under this
License.  Each licensee is addressed as "you".  "Licensees" and
"recipients" may be individuals or organizations.

  To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy.  The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.

  A "covered work" means either the unmodified Program or a work based
on the Program.

  To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy.  Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.

  To "convey" a work means any kind of propagation that enables other
parties to make or receive copies.  Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.

  An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License.  If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.

  1. Source Code.

  The "source code" for a work means the preferred form of the work
for making modifications to it.  "Object code" means any non-source
form of a work.

  A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.

  The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form.  A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.

  The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities.  However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work.  For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.

  The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.

  The Corresponding Source for a work in source code form is that
same work.

  2. Basic Permissions.

  All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met.  This License explicitly affirms your unlimited
permission to run the unmodified Program.  The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work.  This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.

  You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force.  You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright.  Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.

  Conveying under any other circumstances is permitted solely under
the conditions stated below.  Sublicensing is not allowed; section 10
makes it unnecessary.

  3. Protecting Users' Legal Rights From Anti-Circumvention Law.

  No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.

  When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.

  4. Conveying Verbatim Copies.

  You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.

  You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.

  5. Conveying Modified Source Versions.

  You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:

    a) The work must carry prominent notices stating that you modified
    it, and giving a relevant date.

    b) The work must carry prominent notices stating that it is
    released under this License and any conditions added under section
    7.  This requirement modifies the requirement in section 4 to
    "keep intact all notices".

    c) You must license the entire work, as a whole, under this
    License to anyone who comes into possession of a copy.  This
    License will therefore apply, along with any applicable section 7
    additional terms, to the whole of the work, and all its parts,
    regardless of how they are packaged.  This License gives no
    permission to license the work in any other way, but it does not
    invalidate such permission if you have separately received it.

    d) If the work has interactive user interfaces, each must display
    Appropriate Legal Notices; however, if the Program has interactive
    interfaces that do not display Appropriate Legal Notices, your
    work need not make them do so.

  A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit.  Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.

  6. Conveying Non-Source Forms.

  You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:

    a) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by the
    Corresponding Source fixed on a durable physical medium
    customarily used for software interchange.

    b) Convey the object code in, or embodied in, a physical product
    (including a physical distribution medium), accompanied by a
    written offer, valid for at least three years and valid for as
    long as you offer spare parts or customer support for that product
    model, to give anyone who possesses the object code either (1) a
    copy of the Corresponding Source for all the software in the
    product that is covered by this License, on a durable physical
    medium customarily used for software interchange, for a price no
    more than your reasonable cost of physically performing this
    conveying of source, or (2) access to copy the
    Corresponding Source from a network server at no charge.

    c) Convey individual copies of the object code with a copy of the
    written offer to provide the Corresponding Source.  This
    alternative is allowed only occasionally and noncommercially, and
    only if you received the object code with such an offer, in accord
    with subsection 6b.

    d) Convey the object code by offering access from a designated
    place (gratis or for a charge), and offer equivalent access to the
    Corresponding Source in the same way through the same place at no
    further charge.  You need not require recipients to copy the
    Corresponding Source along with the object code.  If the place to
    copy the object code is a network server, the Corresponding Source
    may be on a different server (operated by you or a third party)
    that supports equivalent copying facilities, provided you maintain
    clear directions next to the object code saying where to find the
    Corresponding Source.  Regardless of what server hosts the
    Corresponding Source, you remain obligated to ensure that it is
    available for as long as needed to satisfy these requirements.

    e) Convey the object code using peer-to-peer transmission, provided
    you inform other peers where the object code and Corresponding
    Source of the work are being offered to the general public at no
    charge under subsection 6d.

  A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.

  A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling.  In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage.  For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product.  A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.

  "Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source.  The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.

  If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information.  But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).

  The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed.  Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.

  Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.

  7. Additional Terms.

  "Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law.  If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.

  When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it.  (Additional permissions may be written to require their own
removal in certain cases when you modify the work.)  You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.

  Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:

    a) Disclaiming warranty or limiting liability differently from the
    terms of sections 15 and 16 of this License; or

    b) Requiring preservation of specified reasonable legal notices or
    author attributions in that material or in the Appropriate Legal
    Notices displayed by works containing it; or

    c) Prohibiting misrepresentation of the origin of that material, or
    requiring that modified versions of such material be marked in
    reasonable ways as different from the original version; or

    d) Limiting the use for publicity purposes of names of licensors or
    authors of the material; or

    e) Declining to grant rights under trademark law for use of some
    trade names, trademarks, or service marks; or

    f) Requiring indemnification of licensors and authors of that
    material by anyone who conveys the material (or modified versions of
    it) with contractual assumptions of liability to the recipient, for
    any liability that these contractual assumptions directly impose on
    those licensors and authors.

  All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10.  If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term.  If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.

  If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.

  Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.

  8. Termination.

  You may not propagate or modify a covered work except as expressly
provided under this License.  Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).

  However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.

  Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.

  Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License.  If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.

  9. Acceptance Not Required for Having Copies.

  You are not required to accept this License in order to receive or
run a copy of the Program.  Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance.  However,
nothing other than this License grants you permission to propagate or
modify any covered work.  These actions infringe copyright if you do
not accept this License.  Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.

  10. Automatic Licensing of Downstream Recipients.

  Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License.  You are not responsible
for enforcing compliance by third parties with this License.

  An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations.  If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.

  You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License.  For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.

  11. Patents.

  A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based.  The
work thus licensed is called the contributor's "contributor version".

  A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version.  For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.

  Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.

  In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement).  To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.

  If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients.  "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.

  If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.

  A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License.  You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.

  Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.

  12. No Surrender of Others' Freedom.

  If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License.  If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all.  For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.

  13. Use with the GNU Affero General Public License.

  Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work.  The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.

  14. Revised Versions of this License.

  The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time.  Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.

  Each version is given a distinguishing version number.  If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation.  If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.

  If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.

  Later license versions may give you additional or different
permissions.  However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.

  15. Disclaimer of Warranty.

  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

  16. Limitation of Liability.

  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.

  17. Interpretation of Sections 15 and 16.

  If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.

                     END OF TERMS AND CONDITIONS

            How to Apply These Terms to Your New Programs

  If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.

  To do so, attach the following notices to the program.  It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

    <one line to give the program's name and a brief idea of what it does.>
    Copyright (C) <year>  <name of author>

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <https://www.gnu.org/licenses/>.

Also add information on how to contact you by electronic and paper mail.

  If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

    <program>  Copyright (C) <year>  <name of author>
    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
    This is free software, and you are welcome to redistribute it
    under certain conditions; type `show c' for details.

The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License.  Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".

  You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.

  The GNU General Public License does not permit incorporating your program
into proprietary programs.  If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library.  If this is what you want to do, use the GNU Lesser General
Public License instead of this License.  But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.


================================================
FILE: README.md
================================================
<h3 align="center">
  <img height='48px' src='./images/icon-white.png#gh-dark-mode-only'>
  <img height='48px' src='./images/icon-black.png#gh-light-mode-only'>
</h3>

<h3 align="center">Another <a href="https://github.com/MetaCubeX/mihomo">Mihomo</a> GUI</h3>

<p align="center">
  <a href="https://github.com/mihomo-party-org/clash-party/releases">
    <img src="https://img.shields.io/github/release/mihomo-party-org/clash-party/all.svg">
  </a>
  <a href="https://t.me/mihomo_party_group">
    <img src="https://img.shields.io/badge/Telegram-Group-blue?logo=telegram">
  </a>
</p>
<div align='center'>
<img width='90%' src="./images/preview.jpg">
</div>

### 本项目认证稳定机场推荐:“[狗狗加速](https://party.dginv.click/#/register?code=ARdo0mXx)”

##### [狗狗加速 —— 技术流机场 Doggygo VPN](https://party.dginv.click/#/register?code=ARdo0mXx)

- 高性能海外机场,稳定首选,海外团队,无跑路风险
- Clash Party专属8折优惠码:party,仅有500份
- Party专属链接注册送 3 天,每天 1G 流量 [免费试用](https://party.dginv.click/#/register?code=ARdo0mXx)
- 优惠套餐每月仅需 15.8 元,160G 流量,年付 8 折
- 全球首家支持Hysteria1/2 协议,集群负载均衡设计,高速专线,基于最新UDP quic技术,极低延迟,无视晚高峰,4K 秒开,配合Clash Party食用更省心!
- 解锁流媒体及 ChatGPT
- 官网:[https://狗狗加速.com](https://party.dginv.click/#/register?code=ARdo0mXx)

### 特性

- [x] 一键 Smart Core 规则覆写,基于 AI 模型自动选择最优节点 详细介绍请看 [这里](https://clashparty.org/docs/guide/smart-core)
- [x] 开箱即用,无需服务模式的 Tun
- [x] 多种配色主题可选,UI 焕然一新
- [x] 支持大部分 Mihomo(Clash Meta) 常用配置修改
- [x] 内置 Smart内核 与 Mihomo(Clash Meta) 内核
- [x] 通过 WebDAV 一键备份和恢复配置
- [x] 强大的覆写功能,任意修订配置文件
- [x] 深度集成 Sub-Store,轻松管理订阅

### 安装/使用指南见 [官方文档](https://clashparty.org)


================================================
FILE: aur/mihomo-party/PKGBUILD
================================================
pkgname=mihomo-party
pkgver=0.1.3
pkgrel=1
pkgdesc="Another Mihomo GUI."
arch=('x86_64' 'aarch64')
url="https://github.com/mihomo-party-org/mihomo-party"
license=('GPL3')
conflicts=("$pkgname-git" "$pkgname-bin" "$pkgname-electron" "$pkgname-electron-bin")
depends=('gtk3' 'libnotify' 'nss' 'libxss' 'libxtst' 'xdg-utils' 'at-spi2-core' 'util-linux-libs' 'libsecret')
optdepends=('libappindicator-gtk3: Allow mihomo-party to extend a menu via Ayatana indicators in Unity, KDE or Systray (GTK+ 3 library).')
makedepends=('nodejs' 'pnpm' 'libxcrypt-compat')
install=$pkgname.install
source=(
    "${pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz"
    "${pkgname}.sh"
)
sha256sums=("52d761e9432e17477acb8adb5744676df946476e0eb5210fee2b6d45f497f218"
"242609b1259e999e944b7187f4c03aacba8134a7671ff3a50e2e246b4c4eff48")
options=('!lto')

prepare(){
    cd $srcdir/clash-party-${pkgver}
    sed -i "s/productName: Clash Party/productName: clash-party/" electron-builder.yml
    pnpm install
}

build(){
    cd $srcdir/clash-party-${pkgver}
    pnpm build:linux deb
}

package() {
	cd $srcdir/clash-party-${pkgver}/dist
    bsdtar -xf clash-party-linux-${pkgver}*.deb
    bsdtar -xf data.tar.xz -C "${pkgdir}/"
    chmod +x ${pkgdir}/opt/clash-party/mihomo-party
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-alpha
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-smart
    install -Dm755 "${srcdir}/../${pkgname}.sh" "${pkgdir}/usr/bin/${pkgname}"
    sed -i '3s!/opt/clash-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${pkgname}.desktop"

    chown -R root:root ${pkgdir}
}


================================================
FILE: aur/mihomo-party/mihomo-party.install
================================================
# Colored makepkg-like functions
note() {
    printf "${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\n" "$1"
}

_all_off="$(tput sgr0)"
_bold="${_all_off}$(tput bold)"
_blue="${_bold}$(tput setaf 4)"
_yellow="${_bold}$(tput setaf 3)"

post_install() {
    note "Custom flags should be put directly in: ~/.config/mihomo-party-flags.conf"
    note "The launcher is called: 'mihomo-party'"
}

================================================
FILE: aur/mihomo-party/mihomo-party.sh
================================================
#!/usr/bin/bash

XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}

# Allow users to override command-line options
if [[ -f "${XDG_CONFIG_HOME}/mihomo-party-flags.conf" ]]; then
	mapfile -t MIHOMO_PARTY_USER_FLAGS <<<"$(grep -v '^#' "${XDG_CONFIG_HOME}/mihomo-party-flags.conf")"
	echo "User flags:" ${MIHOMO_PARTY_USER_FLAGS[@]}
fi

# Launch
exec /opt/clash-party/mihomo-party ${MIHOMO_PARTY_USER_FLAGS[@]} "$@"


================================================
FILE: aur/mihomo-party-bin/PKGBUILD
================================================
pkgname=mihomo-party-bin
_pkgname=mihomo-party
pkgver=0.1.3
pkgrel=1
pkgdesc="Another Mihomo GUI."
arch=('x86_64' 'aarch64')
url="https://github.com/mihomo-party-org/mihomo-party"
license=('GPL3')
conflicts=("$_pkgname" "$_pkgname-git" "$_pkgname-electron" "$_pkgname-electron-bin")
conflicts=("mihomo-party-git" 'mihomo-party')
depends=('gtk3' 'libnotify' 'nss' 'libxss' 'libxtst' 'xdg-utils' 'at-spi2-core' 'util-linux-libs' 'libsecret')
optdepends=('libappindicator-gtk3: Allow mihomo-party to extend a menu via Ayatana indicators in Unity, KDE or Systray (GTK+ 3 library).')
install=$_pkgname.install
source=("${_pkgname}.sh")
source_x86_64=("${_pkgname}-${pkgver}-x86_64.deb::${url}/releases/download/v${pkgver}/clash-party-linux-${pkgver}-amd64.deb")
source_aarch64=("${_pkgname}-${pkgver}-aarch64.deb::${url}/releases/download/v${pkgver}/clash-party-linux-${pkgver}-arm64.deb")
sha256sums=('242609b1259e999e944b7187f4c03aacba8134a7671ff3a50e2e246b4c4eff48')
sha256sums_x86_64=('b8d166f1134573336aaae1866d25262284b0cbabbf393684226aca0fd8d1bd83')
sha256sums_aarch64=('8cd7398b8fc1cd70d41e386af9995cbddc1043d9018391c29f056f1435712a10')

package() {
    bsdtar -xf data.tar.xz -C "${pkgdir}/"
    chmod +x ${pkgdir}/opt/clash-party/mihomo-party
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-alpha
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-smart
    install -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
    sed -i '3s!/opt/clash-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"

    chown -R root:root ${pkgdir}
}


================================================
FILE: aur/mihomo-party-bin/mihomo-party.install
================================================
# Colored makepkg-like functions
note() {
    printf "${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\n" "$1"
}

_all_off="$(tput sgr0)"
_bold="${_all_off}$(tput bold)"
_blue="${_bold}$(tput setaf 4)"
_yellow="${_bold}$(tput setaf 3)"

post_install() {
    note "Custom flags should be put directly in: ~/.config/mihomo-party-flags.conf"
    note "The launcher is called: 'mihomo-party'"
}

================================================
FILE: aur/mihomo-party-bin/mihomo-party.sh
================================================
#!/usr/bin/bash

XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}

# Allow users to override command-line options
if [[ -f "${XDG_CONFIG_HOME}/mihomo-party-flags.conf" ]]; then
	mapfile -t MIHOMO_PARTY_USER_FLAGS <<<"$(grep -v '^#' "${XDG_CONFIG_HOME}/mihomo-party-flags.conf")"
	echo "User flags:" ${MIHOMO_PARTY_USER_FLAGS[@]}
fi

# Launch
exec /opt/clash-party/mihomo-party ${MIHOMO_PARTY_USER_FLAGS[@]} "$@"


================================================
FILE: aur/mihomo-party-electron/PKGBUILD
================================================
pkgname=mihomo-party-electron
_pkgname=mihomo-party
pkgver=0.1.3
pkgrel=1
pkgdesc="Another Mihomo GUI."
arch=('x86_64' 'aarch64')
url="https://github.com/mihomo-party-org/mihomo-party"
license=('GPL3')
conflicts=("$_pkgname" "$_pkgname-git" "$_pkgname-bin" "$_pkgname-electron-bin")
depends=('electron' 'gtk3' 'libnotify' 'nss' 'libxss' 'libxtst' 'xdg-utils' 'at-spi2-core' 'util-linux-libs' 'libsecret')
optdepends=('libappindicator-gtk3: Allow mihomo-party to extend a menu via Ayatana indicators in Unity, KDE or Systray (GTK+ 3 library).')
makedepends=('nodejs' 'pnpm' 'libxcrypt-compat' 'asar')
install=$_pkgname.install
source=(
    "${_pkgname}-${pkgver}.tar.gz::${url}/archive/refs/tags/v${pkgver}.tar.gz"
    "${_pkgname}.desktop"
    "${_pkgname}.sh"
)
sha256sums=("52d761e9432e17477acb8adb5744676df946476e0eb5210fee2b6d45f497f218"
"96a6250f67517493f839f964c024434dbcf784b25a73f074bb505f1521f52844"
"87fddbcd4a4cc7bda22ec4cadff0040e54395bb13184ee4688b58788c1fa7180"
)
options=('!lto')

prepare(){
    cd $srcdir/clash-party-${pkgver}
    sed -i "s/productName: Clash Party/productName: clash-party/" electron-builder.yml
    pnpm install
}

build(){
    cd $srcdir/${_pkgname}-${pkgver}
    pnpm build:linux deb
}

package() {
    asar extract $srcdir/${_pkgname}-${pkgver}/dist/linux-unpacked/resources/app.asar ${pkgdir}/opt/mihomo-party
    cp -r $srcdir/${_pkgname}-${pkgver}/extra/sidecar ${pkgdir}/opt/mihomo-party/resources/
    cp -r $srcdir/${_pkgname}-${pkgver}/extra/files ${pkgdir}/opt/mihomo-party/resources/
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-alpha
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-smart
    install -Dm755 "${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
    install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
    install -Dm644 "${pkgdir}/opt/clash-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png"

    chown -R root:root ${pkgdir}
}


================================================
FILE: aur/mihomo-party-electron/mihomo-party.desktop
================================================
[Desktop Entry]
Name=Clash Party
Exec=mihomo-party %U
Terminal=false
Type=Application
Icon=mihomo-party
StartupWMClass=mihomo-party
MimeType=x-scheme-handler/clash;x-scheme-handler/mihomo;
Comment=Clash Party
Categories=Utility;


================================================
FILE: aur/mihomo-party-electron/mihomo-party.install
================================================
# Colored makepkg-like functions
note() {
    printf "${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\n" "$1"
}

_all_off="$(tput sgr0)"
_bold="${_all_off}$(tput bold)"
_blue="${_bold}$(tput setaf 4)"
_yellow="${_bold}$(tput setaf 3)"

post_install() {
    note "Custom flags should be put directly in: ~/.config/mihomo-party-flags.conf"
    note "The launcher is called: 'mihomo-party'"
}

================================================
FILE: aur/mihomo-party-electron/mihomo-party.sh
================================================
#!/usr/bin/bash

XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}

# Allow users to override command-line options
if [[ -f "${XDG_CONFIG_HOME}/mihomo-party-flags.conf" ]]; then
	mapfile -t MIHOMO_PARTY_USER_FLAGS <<<"$(grep -v '^#' "${XDG_CONFIG_HOME}/mihomo-party-flags.conf")"
	echo "User flags:" ${MIHOMO_PARTY_USER_FLAGS[@]}
fi

# Launch
exec electron /opt/clash-party ${MIHOMO_PARTY_USER_FLAGS[@]} "$@"


================================================
FILE: aur/mihomo-party-electron-bin/PKGBUILD
================================================
pkgname=mihomo-party-electron-bin
_pkgname=mihomo-party
pkgver=0.2.2
pkgrel=1
pkgdesc="Another Mihomo GUI."
arch=('x86_64' 'aarch64')
url="https://github.com/mihomo-party-org/mihomo-party"
license=('GPL3')
conflicts=("$_pkgname" "$_pkgname-git" "$_pkgname-bin" "$_pkgname-electron")
depends=('electron' 'gtk3' 'libnotify' 'nss' 'libxss' 'libxtst' 'xdg-utils' 'at-spi2-core' 'util-linux-libs' 'libsecret')
optdepends=('libappindicator-gtk3: Allow mihomo-party to extend a menu via Ayatana indicators in Unity, KDE or Systray (GTK+ 3 library).')
makedepends=('asar')
install=$_pkgname.install
source=("${_pkgname}.desktop" "${_pkgname}.sh")
source_x86_64=("${_pkgname}-${pkgver}-x86_64.deb::${url}/releases/download/v${pkgver}/clash-party-linux-${pkgver}-amd64.deb")
source_aarch64=("${_pkgname}-${pkgver}-aarch64.deb::${url}/releases/download/v${pkgver}/clash-party-linux-${pkgver}-arm64.deb")
sha256sums=(
    "96a6250f67517493f839f964c024434dbcf784b25a73f074bb505f1521f52844"
    "87fddbcd4a4cc7bda22ec4cadff0040e54395bb13184ee4688b58788c1fa7180"
)
sha256sums_x86_64=("43f8b9a5818a722cdb8e5044d2a90993274860b0da96961e1a2652169539ce39")
sha256sums_aarch64=("18574fdeb01877a629aa52ac0175335ce27c83103db4fcb2f1ad69e3e42ee10f")
options=('!lto')

package() {
    bsdtar -xf data.tar.xz -C $srcdir
    asar extract $srcdir/opt/clash-party/resources/app.asar ${pkgdir}/opt/mihomo-party
    cp -r $srcdir/opt/clash-party/resources/sidecar ${pkgdir}/opt/mihomo-party/resources/
    cp -r $srcdir/opt/clash-party/resources/files ${pkgdir}/opt/mihomo-party/resources/
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-alpha
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-smart
    install -Dm755 "${srcdir}/${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
    install -Dm644 "${_pkgname}.desktop" "${pkgdir}/usr/share/applications/${_pkgname}.desktop"
    install -Dm644 "${pkgdir}/opt/clash-party/resources/icon.png" "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_pkgname}.png"

    chown -R root:root ${pkgdir}
}


================================================
FILE: aur/mihomo-party-electron-bin/mihomo-party.desktop
================================================
[Desktop Entry]
Name=Clash Party
Exec=mihomo-party %U
Terminal=false
Type=Application
Icon=mihomo-party
StartupWMClass=mihomo-party
MimeType=x-scheme-handler/clash;x-scheme-handler/mihomo;
Comment=Clash Party
Categories=Utility;


================================================
FILE: aur/mihomo-party-electron-bin/mihomo-party.install
================================================
# Colored makepkg-like functions
note() {
    printf "${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\n" "$1"
}

_all_off="$(tput sgr0)"
_bold="${_all_off}$(tput bold)"
_blue="${_bold}$(tput setaf 4)"
_yellow="${_bold}$(tput setaf 3)"

post_install() {
    note "Custom flags should be put directly in: ~/.config/mihomo-party-flags.conf"
    note "The launcher is called: 'mihomo-party'"
}

================================================
FILE: aur/mihomo-party-electron-bin/mihomo-party.sh
================================================
#!/usr/bin/bash

XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}

# Allow users to override command-line options
if [[ -f "${XDG_CONFIG_HOME}/mihomo-party-flags.conf" ]]; then
	mapfile -t MIHOMO_PARTY_USER_FLAGS <<<"$(grep -v '^#' "${XDG_CONFIG_HOME}/mihomo-party-flags.conf")"
	echo "User flags:" ${MIHOMO_PARTY_USER_FLAGS[@]}
fi

# Launch
exec electron /opt/clash-party ${MIHOMO_PARTY_USER_FLAGS[@]} "$@"


================================================
FILE: aur/mihomo-party-git/PKGBUILD
================================================
pkgname=mihomo-party-git
_pkgname=${pkgname%-git}
pkgver=0.1.3.r5.g5f5d6dd
pkgrel=1
pkgdesc="Another Mihomo GUI."
arch=('x86_64' 'aarch64')
url="https://github.com/mihomo-party-org/mihomo-party"
license=('GPL3')
conflicts=("$_pkgname" "$_pkgname-bin" "$_pkgname-electron" "$_pkgname-electron-bin")
depends=('gtk3' 'libnotify' 'nss' 'libxss' 'libxtst' 'xdg-utils' 'at-spi2-core' 'util-linux-libs' 'libsecret')
optdepends=('libappindicator-gtk3: Allow mihomo-party to extend a menu via Ayatana indicators in Unity, KDE or Systray (GTK+ 3 library).')
makedepends=('nodejs' 'pnpm' 'jq' 'libxcrypt-compat')
install=$_pkgname.install
source=("${_pkgname}.sh" "git+$url.git")
sha256sums=("242609b1259e999e944b7187f4c03aacba8134a7671ff3a50e2e246b4c4eff48" "SKIP")
options=('!lto')

pkgver() {
    cd $srcdir/${_pkgname}
    ( set -o pipefail
        git describe --long 2>/dev/null | sed 's/\([^-]*-g\)/r\1/;s/-/./g' | tr -d 'v' ||
        printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)"
    )
}

prepare(){
    cd $srcdir/${_pkgname}
    sed -i "s/productName: Clash Party/productName: clash-party/" electron-builder.yml
    pnpm install
}

build(){
    cd $srcdir/${_pkgname}
    pnpm build:linux deb
}

package() {
	cd $srcdir/${_pkgname}/dist
    bsdtar -xf clash-party-linux-$(jq '.version' $srcdir/${_pkgname}/package.json | tr -d 'v"')*.deb
    bsdtar -xf data.tar.xz -C "${pkgdir}/"
    chmod +x ${pkgdir}/opt/clash-party/mihomo-party
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-alpha
    chmod +sx ${pkgdir}/opt/clash-party/resources/sidecar/mihomo-smart
    install -Dm755 "${srcdir}/../${_pkgname}.sh" "${pkgdir}/usr/bin/${_pkgname}"
    sed -i '3s!/opt/clash-party/mihomo-party!mihomo-party!' "${pkgdir}/usr/share/applications/${_pkgname}.desktop"

    chown -R root:root ${pkgdir}
}


================================================
FILE: aur/mihomo-party-git/mihomo-party.install
================================================
# Colored makepkg-like functions
note() {
    printf "${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\n" "$1"
}

_all_off="$(tput sgr0)"
_bold="${_all_off}$(tput bold)"
_blue="${_bold}$(tput setaf 4)"
_yellow="${_bold}$(tput setaf 3)"

post_install() {
    note "Custom flags should be put directly in: ~/.config/mihomo-party-flags.conf"
    note "The launcher is called: 'mihomo-party'"
}

================================================
FILE: aur/mihomo-party-git/mihomo-party.sh
================================================
#!/usr/bin/bash

XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}

# Allow users to override command-line options
if [[ -f "${XDG_CONFIG_HOME}/mihomo-party-flags.conf" ]]; then
	mapfile -t MIHOMO_PARTY_USER_FLAGS <<<"$(grep -v '^#' "${XDG_CONFIG_HOME}/mihomo-party-flags.conf")"
	echo "User flags:" ${MIHOMO_PARTY_USER_FLAGS[@]}
fi

# Launch
exec /opt/clash-party/mihomo-party ${MIHOMO_PARTY_USER_FLAGS[@]} "$@"


================================================
FILE: build/entitlements.mac.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>com.apple.security.cs.allow-jit</key>
    <true/>
    <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
    <true/>
    <key>com.apple.security.cs.allow-dyld-environment-variables</key>
    <true/>
    <key>com.apple.security.cs.disable-library-validation</key>
    <true/>
  </dict>
</plist>


================================================
FILE: build/linux/postinst
================================================
#!/bin/bash

set -e

if type update-alternatives >/dev/null 2>&1; then
    # Remove previous link if it doesn't use update-alternatives
    if [ -L '/usr/bin/clash-party' ] && [ -e '/usr/bin/clash-party' ] && [ "$(readlink '/usr/bin/clash-party')" != '/etc/alternatives/clash-party' ]; then
        rm -f '/usr/bin/clash-party'
    fi
    update-alternatives --install '/usr/bin/clash-party' 'clash-party' '/opt/clash-party/mihomo-party' 100 || ln -sf '/opt/clash-party/mihomo-party' '/usr/bin/clash-party'
else
    ln -sf '/opt/clash-party/mihomo-party' '/usr/bin/clash-party'
fi

chmod 4755 '/opt/clash-party/chrome-sandbox' 2>/dev/null || true

chmod +sx /opt/clash-party/resources/sidecar/mihomo 2>/dev/null || true
chmod +sx /opt/clash-party/resources/sidecar/mihomo-alpha 2>/dev/null || true
chmod +sx /opt/clash-party/resources/sidecar/mihomo-smart 2>/dev/null || true

if hash update-mime-database 2>/dev/null; then
    update-mime-database /usr/share/mime || true
fi

if hash update-desktop-database 2>/dev/null; then
    update-desktop-database /usr/share/applications || true
fi

# Update icon cache for GNOME/GTK environments
if hash gtk-update-icon-cache 2>/dev/null; then
    for dir in /usr/share/icons/hicolor /usr/share/icons/gnome; do
        [ -d "$dir" ] && gtk-update-icon-cache -f -t "$dir" 2>/dev/null || true
    done
fi

# Refresh GNOME Shell icon cache
if hash update-icon-caches 2>/dev/null; then
    update-icon-caches /usr/share/icons/* 2>/dev/null || true
fi


================================================
FILE: build/linux/postuninst
================================================
#!/bin/bash

case "$1" in
  remove|purge|0)
    if type update-alternatives >/dev/null 2>&1; then
        update-alternatives --remove 'clash-party' '/opt/clash-party/mihomo-party' 2>/dev/null || true
    fi
    
    [ -L '/usr/bin/clash-party' ] && rm -f '/usr/bin/clash-party'

    if hash update-mime-database 2>/dev/null; then
      update-mime-database /usr/share/mime || true
    fi

    if hash update-desktop-database 2>/dev/null; then
        update-desktop-database /usr/share/applications || true
    fi

    # Update icon cache
    if hash gtk-update-icon-cache 2>/dev/null; then
        for dir in /usr/share/icons/hicolor /usr/share/icons/gnome; do
            [ -d "$dir" ] && gtk-update-icon-cache -f -t "$dir" 2>/dev/null || true
        done
    fi
    ;;
  *)
    # others
    ;;
esac


================================================
FILE: build/pkg-scripts/postinstall
================================================
#!/bin/bash
set -e

# 设置日志文件
LOG_FILE="/tmp/mihomo-party-install.log"
exec > "$LOG_FILE" 2>&1

log() {
    echo "$(date '+%Y-%m-%d %H:%M:%S') - $1"
}

# 检查 root 权限
if [ "$EUID" -ne 0 ]; then
    log "Error: Please run as root"
    exit 1
fi

# 判断 $2 是否以 .app 结尾
if [[ $2 == *".app" ]]; then
    APP_PATH="$2"
else
    APP_PATH="$2/Clash Party.app"
fi

HELPER_PATH="/Library/PrivilegedHelperTools/party.mihomo.helper"
LAUNCH_DAEMON="/Library/LaunchDaemons/party.mihomo.helper.plist"

log "Starting installation..."

# 创建目录并设置权限
log "Creating directories and setting permissions..."
mkdir -p "/Library/PrivilegedHelperTools"
chmod 755 "/Library/PrivilegedHelperTools"
chown root:wheel "/Library/PrivilegedHelperTools"

# 设置核心文件权限
log "Setting core file permissions..."
if [ -f "$APP_PATH/Contents/Resources/sidecar/mihomo" ]; then
    chown root:admin "$APP_PATH/Contents/Resources/sidecar/mihomo"
    chmod +s "$APP_PATH/Contents/Resources/sidecar/mihomo"
    log "Set permissions for mihomo"
else
    log "Warning: mihomo binary not found at $APP_PATH/Contents/Resources/sidecar/mihomo"
fi

if [ -f "$APP_PATH/Contents/Resources/sidecar/mihomo-alpha" ]; then
    chown root:admin "$APP_PATH/Contents/Resources/sidecar/mihomo-alpha"
    chmod +s "$APP_PATH/Contents/Resources/sidecar/mihomo-alpha"
    log "Set permissions for mihomo-alpha"
else
    log "Warning: mihomo-alpha binary not found at $APP_PATH/Contents/Resources/sidecar/mihomo-alpha"
fi

if [ -f "$APP_PATH/Contents/Resources/sidecar/mihomo-smart" ]; then
    chown root:admin "$APP_PATH/Contents/Resources/sidecar/mihomo-smart"
    chmod +s "$APP_PATH/Contents/Resources/sidecar/mihomo-smart"
    log "Set permissions for mihomo-smart"
else
    log "Warning: mihomo-smart binary not found at $APP_PATH/Contents/Resources/sidecar/mihomo-smart"
fi

# 复制 helper 工具
log "Installing helper tool..."
if [ -f "$APP_PATH/Contents/Resources/files/party.mihomo.helper" ]; then
    cp -f "$APP_PATH/Contents/Resources/files/party.mihomo.helper" "$HELPER_PATH"
    chown root:wheel "$HELPER_PATH"
    chmod 544 "$HELPER_PATH"
    log "Helper tool installed successfully"
else
    log "Error: Helper file not found at $APP_PATH/Contents/Resources/files/party.mihomo.helper"
    exit 1
fi

# 创建并配置 LaunchDaemon
log "Configuring LaunchDaemon..."
mkdir -p "/Library/LaunchDaemons"
cat << EOF > "$LAUNCH_DAEMON"
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>party.mihomo.helper</string>
    <key>AssociatedBundleIdentifiers</key>
    <array>
        <string>party.mihomo.app</string>
    </array>
    <key>KeepAlive</key>
    <true/>
    <key>Program</key>
    <string>${HELPER_PATH}</string>
    <key>StandardErrorPath</key>
    <string>/tmp/party.mihomo.helper.err</string>
    <key>StandardOutPath</key>
    <string>/tmp/party.mihomo.helper.log</string>
</dict>
</plist>
EOF

chown root:wheel "$LAUNCH_DAEMON"
chmod 644 "$LAUNCH_DAEMON"
log "LaunchDaemon configured"

# 验证关键文件
log "Verifying installation..."
if [ ! -x "$HELPER_PATH" ]; then
    log "Error: Helper tool is not executable: $HELPER_PATH"
    exit 1
fi

# 检查二进制文件有效性
if ! file "$HELPER_PATH" | grep -q "Mach-O"; then
    log "Error: Helper tool is not a valid Mach-O binary"
    exit 1
fi

# 验证 plist 格式
if ! plutil -lint "$LAUNCH_DAEMON" >/dev/null 2>&1; then
    log "Error: Invalid plist format"
    exit 1
fi

# 获取 macOS 版本
macos_version=$(sw_vers -productVersion)
macos_major=$(echo "$macos_version" | cut -d. -f1)
log "macOS version: $macos_version"

# 启用服务(防止安全软件禁用)
if ! launchctl enable system/party.mihomo.helper 2>/dev/null; then
    log "Warning: Failed to enable service, continuing installation..."
else
    log "Service enabled successfully"
fi

# 清理现有服务
log "Cleaning up existing services..."
launchctl bootout system "$LAUNCH_DAEMON" 2>/dev/null || true
launchctl unload "$LAUNCH_DAEMON" 2>/dev/null || true

# 加载服务
log "Loading service..."
if [ "$macos_major" -ge 11 ]; then
    # macOS Big Sur 及更新版本使用 bootstrap
    if ! launchctl bootstrap system "$LAUNCH_DAEMON"; then
        log "Bootstrap failed, trying legacy load..."
        if ! launchctl load "$LAUNCH_DAEMON"; then
            log "Error: Failed to load service with both methods"
            exit 1
        fi
    fi
else
    # 旧版本使用 load
    if ! launchctl load "$LAUNCH_DAEMON"; then
        log "Error: Failed to load service"
        exit 1
    fi
fi

# 验证服务状态
log "Verifying service status..."
sleep 2
if launchctl list | grep -q "party.mihomo.helper"; then
    log "Service loaded successfully"
else
    log "Warning: Service may not be running properly"
fi

log "Installation completed successfully"

# Fix user data directory permissions
log "Fixing user data directory permissions..."
for user_home in /Users/*; do
    if [ -d "$user_home" ] && [ "$(basename "$user_home")" != "Shared" ] && [ "$(basename "$user_home")" != ".localized" ]; then
        username=$(basename "$user_home")
        user_data_dir="$user_home/Library/Application Support/mihomo-party"

        if [ -d "$user_data_dir" ]; then
            current_owner=$(stat -f "%Su" "$user_data_dir" 2>/dev/null || echo "unknown")
            if [ "$current_owner" = "root" ]; then
                log "Fixing ownership for user: $username"
                chown -R "$username:staff" "$user_data_dir" 2>/dev/null || true
                chmod -R u+rwX "$user_data_dir" 2>/dev/null || true
            fi
        fi
    fi
done

exit 0


================================================
FILE: build/pkg-scripts/preinstall
================================================
#!/bin/bash
set -e

# 检查 root 权限
if [ "$EUID" -ne 0 ]; then
    echo "Please run as root"
    exit 1
fi

HELPER_PATH="/Library/PrivilegedHelperTools/party.mihomo.helper"
LAUNCH_DAEMON="/Library/LaunchDaemons/party.mihomo.helper.plist"

# 停止并卸载现有的 LaunchDaemon
if [ -f "$LAUNCH_DAEMON" ]; then
    launchctl unload "$LAUNCH_DAEMON" 2>/dev/null || true
    rm -f "$LAUNCH_DAEMON"
fi

# 移除 helper 工具
rm -f "$HELPER_PATH"

# 清理可能存在的旧版本文件
rm -rf "/Applications/Clash Party.app"
rm -rf "/Applications/Clash\\ Party.app"
rm -rf "/Applications/Mihomo Party.app"
rm -rf "/Applications/Mihomo\\ Party.app"

exit 0


================================================
FILE: changelog.md
================================================
# 1.9.2

## 新功能 (Feat)

- 增加 Fish 和 Nushell 环境变量支持
- 增加规则统计和禁用开关
- 连接页面显示应用图标
- 增加订阅超时设置
- 增加 Windows 7 兼容构建支持

## 修复 (Fix)

- 修复 group.all 过滤器中代理未定义导致的错误
- 修复默认延迟测试 URL 改为 HTTPS
- 修复连接图表 z-index 和缩放问题
- 修复减少动画模式下侧边栏卡片拖拽动画异常
- 修复 IME 输入法输入时字符重复问题
- 修复配置切换失败后切换队列损坏问题
- 修复取消最大化状态未保存的问题
- 优化备份配置导入处理
- 修复禁用自动滚动时日志显示未冻结的问题
- 修复订阅页面相对时间未自动刷新的问题
- 修复连接数badge遮挡下方关闭按钮点击的问题

## 优化 (Optimize)

- 虚拟化规则编辑弹窗中的规则列表,提升渲染性能
- 优化内核启动性能
- 重构外部控制器和规则编辑器
- 优化订阅更新逻辑效率

## 其他 (Chore)

- 更新依赖

# 1.9.1

## 修复 (Fix)

- 修复 Windows 下以管理员重启开启 TUN 时因单实例锁冲突导致的闪退问题
- 修复托盘菜单开启 TUN 时管理员重启后继续执行导致的竞态问题
- 修复关键资源文件复制失败时静默跳过导致内核启动异常的问题

# 1.9.0

## 新功能 (Feat)

- 支持隐藏不可用代理选项
- 支持禁用自动更新
- 支持交换任务栏点击行为
- 支持订阅导入时自动选择直连或代理
- 增加 WebDAV 证书忽略选项
- 增加 mrs ruleset 预览支持
- 增加认证令牌支持
- 增加详细错误提示并支持复制功能
- 托盘代理组样式支持子菜单模式
- 增加繁体中文(台湾)翻译
- 增加 HTML 检测和配置文件解析错误处理
- 将 sysproxy 迁移至 sysproxy-rs

## 修复 (Fix)

- 修复 Windows 旧 mihomo 进程导致的 EBUSY 错误
- 修复侧边栏卡片水平偏移
- macOS DNS 设置使用 helper 服务避免权限问题
- 修复首次启动时资源文件复制失败导致程序无法运行的问题
- 修复连接详情和日志无法选择的问题
- 修复 mixed-port 配置问题
- 空端口输入处理为 0
- 修复覆盖页面中缺失的占位符和错误处理
- 修复内核自重启时的竞态条件
- 修复端口值为 NaN 时的配置读写问题
- 修复 Smart Core 代理组名称替换精度问题
- 修复 profile/override 配置中 items 数组未定义导致的错误
- 修复 lite 模式下 geo 文件同步到 profile 工作目录
- 修复 Linux GNOME 桌面图标和启动器可见性问题
- 修复管理员重启时等待新进程启动

## 优化 (Optimize)

- 使用通知系统替换 alert() 弹窗
- 优化连接页面性能



================================================
FILE: electron-builder.yml
================================================
appId: party.mihomo.app
productName: Clash Party
directories:
  buildResources: build
files:
  - '!**/.vscode/*'
  - '!src/*'
  - '!aur/*'
  - '!images/*'
  - '!scripts/*'
  - '!extra/*'
  - '!tailwind.config.js'
  - '!postcss.config.js'
  - '!electron.vite.config.{js,ts,mjs,cjs}'
  - '!{.eslintignore,.eslintrc.cjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
  - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
  - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
extraResources:
  - from: './extra/'
    to: ''
protocols:
  name: 'Clash Party URI Scheme'
  schemes:
    - 'clash'
    - 'mihomo'
win:
  target:
    - nsis
    - 7z
  artifactName: clash-party-windows-${version}-${arch}-portable.${ext}
nsis:
  artifactName: clash-party-windows-${version}-${arch}-setup.${ext}
  uninstallDisplayName: ${productName}
  allowToChangeInstallationDirectory: true
  oneClick: false
  perMachine: true
  createDesktopShortcut: always
mac:
  target:
    - pkg
  entitlementsInherit: build/entitlements.mac.plist
  hardenedRuntime: true
  gatekeeperAssess: false
  extendInfo:
    - NSCameraUsageDescription: Application requests access to the device's camera.
    - NSMicrophoneUsageDescription: Application requests access to the device's microphone.
    - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
    - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
  notarize: false
  artifactName: clash-party-macos-${version}-${arch}.${ext}
pkg:
  allowAnywhere: false
  allowCurrentUserHome: false
  isRelocatable: false
  background:
    alignment: bottomleft
    file: build/background.png
linux:
  executableName: mihomo-party
  icon: build/icon.png
  desktop:
    entry:
      Name: Clash Party
      GenericName: Proxy Client
      Comment: A GUI client based on Mihomo
      MimeType: 'x-scheme-handler/clash;x-scheme-handler/mihomo'
      Keywords: proxy;clash;mihomo;vpn;
      StartupWMClass: mihomo-party
      Icon: mihomo-party
  target:
    - deb
    - rpm
  maintainer: mihomo-party-org
  category: Utility
  artifactName: clash-party-linux-${version}-${arch}.${ext}
deb:
  afterInstall: 'build/linux/postinst'
  afterRemove: 'build/linux/postuninst'
rpm:
  afterInstall: 'build/linux/postinst'
  afterRemove: 'build/linux/postuninst'
  fpm:
    - '--rpm-rpmbuild-define'
    - '_build_id_links none'
npmRebuild: true
publish: []


================================================
FILE: electron.vite.config.ts
================================================
import { resolve } from 'path'
import { defineConfig, externalizeDepsPlugin } from 'electron-vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// https://github.com/vdesjs/vite-plugin-monaco-editor/issues/21#issuecomment-1827562674
import monacoEditorPluginModule from 'vite-plugin-monaco-editor'
const isObjectWithDefaultFunction = (
  module: unknown
): module is { default: typeof monacoEditorPluginModule } =>
  module != null &&
  typeof module === 'object' &&
  'default' in module &&
  typeof module.default === 'function'
const monacoEditorPlugin = isObjectWithDefaultFunction(monacoEditorPluginModule)
  ? monacoEditorPluginModule.default
  : monacoEditorPluginModule

// Win7 build: bundle all deps (Vite converts ESM→CJS), only externalize native modules
const isLegacyBuild = process.env.LEGACY_BUILD === 'true'
const legacyExternal = ['sysproxy-rs', 'electron']

export default defineConfig({
  main: {
    plugins: isLegacyBuild ? [] : [externalizeDepsPlugin()],
    build: isLegacyBuild
      ? { rollupOptions: { external: legacyExternal, output: { format: 'cjs' } } }
      : undefined
  },
  preload: {
    plugins: isLegacyBuild ? [] : [externalizeDepsPlugin()],
    build: {
      rollupOptions: {
        external: isLegacyBuild ? legacyExternal : undefined,
        output: {
          format: 'cjs',
          entryFileNames: '[name].cjs'
        }
      }
    }
  },
  renderer: {
    build: {
      rollupOptions: {
        input: {
          index: resolve('src/renderer/index.html'),
          floating: resolve('src/renderer/floating.html')
        }
      }
    },
    resolve: {
      alias: {
        '@renderer': resolve('src/renderer/src')
      }
    },
    plugins: [
      react(),
      tailwindcss(),
      monacoEditorPlugin({
        languageWorkers: ['editorWorkerService', 'typescript', 'css'],
        customDistPath: (_, out) => `${out}/monacoeditorwork`,
        customWorkers: [
          {
            label: 'yaml',
            entry: 'monaco-yaml/yaml.worker'
          }
        ]
      })
    ]
  }
})


================================================
FILE: eslint.config.cjs
================================================
const js = require('@eslint/js')
const react = require('eslint-plugin-react')
const reactHooks = require('eslint-plugin-react-hooks')
const importPlugin = require('eslint-plugin-import')
const { configs } = require('@electron-toolkit/eslint-config-ts')

module.exports = [
  {
    ignores: ['**/node_modules/**', '**/dist/**', '**/out/**', '**/extra/**', '**/src/native/**']
  },

  js.configs.recommended,
  ...configs.recommended,

  {
    files: ['**/*.{js,jsx,ts,tsx}'],
    plugins: {
      react: react,
      'react-hooks': reactHooks,
      import: importPlugin
    },
    rules: {
      ...react.configs.recommended.rules,
      ...react.configs['jsx-runtime'].rules,
      // React Hooks 规则
      'react-hooks/rules-of-hooks': 'error',
      'react-hooks/exhaustive-deps': 'warn',
      // Import 规则
      'import/no-duplicates': 'warn',
      'import/order': [
        'warn',
        {
          groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'],
          'newlines-between': 'never'
        }
      ],
      // 代码质量
      'no-console': ['warn', { allow: ['warn', 'error'] }],
      'no-debugger': 'warn',
      eqeqeq: ['error', 'always', { null: 'ignore' }],
      'prefer-const': 'warn'
    },
    settings: {
      react: {
        version: 'detect'
      }
    },
    languageOptions: {
      ...react.configs.recommended.languageOptions
    }
  },

  {
    files: ['**/*.cjs', '**/*.mjs', '**/tailwind.config.js', '**/postcss.config.js'],
    rules: {
      '@typescript-eslint/no-require-imports': 'off',
      '@typescript-eslint/explicit-function-return-type': 'off'
    }
  },

  {
    files: ['**/*.{ts,tsx}'],
    rules: {
      '@typescript-eslint/no-unused-vars': [
        'warn',
        { argsIgnorePattern: '^_', varsIgnorePattern: '^_' }
      ],
      '@typescript-eslint/explicit-function-return-type': 'off',
      '@typescript-eslint/no-explicit-any': 'warn',
      '@typescript-eslint/no-non-null-assertion': 'warn'
    }
  },

  {
    files: ['**/logger.ts'],
    rules: {
      'no-console': 'off'
    }
  }
]


================================================
FILE: package.json
================================================
{
  "name": "mihomo-party",
  "version": "1.9.2",
  "description": "Clash Party",
  "type": "module",
  "main": "./out/main/index.js",
  "author": "mihomo-party-org",
  "homepage": "https://clashparty.org",
  "scripts": {
    "format": "prettier --write .",
    "lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix",
    "lint:check": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts",
    "review": "pnpm run lint:check && pnpm run typecheck",
    "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
    "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
    "typecheck": "pnpm run typecheck:node && pnpm run typecheck:web",
    "prepare": "node scripts/prepare.mjs",
    "prepare:dev": "node scripts/update-version.mjs && node scripts/prepare.mjs",
    "updater": "node scripts/updater.mjs",
    "checksum": "node scripts/checksum.mjs",
    "copy-legacy": "node scripts/copy-legacy-artifacts.mjs",
    "test-copy-legacy": "node scripts/test-copy-legacy.mjs",
    "telegram": "node scripts/telegram.mjs release",
    "telegram:dev": "node scripts/telegram.mjs dev",
    "artifact": "node scripts/artifact.mjs",
    "dev": "electron-vite dev",
    "postinstall": "electron-builder install-app-deps && node node_modules/electron/install.js",
    "build:win": "electron-vite build && electron-builder --publish never --win",
    "build:win:dev": "pnpm run prepare:dev && electron-vite build && electron-builder --publish never --win",
    "build:mac": "electron-vite build && electron-builder --publish never --mac",
    "build:mac:dev": "pnpm run prepare:dev && electron-vite build && electron-builder --publish never --mac",
    "build:linux": "electron-vite build && electron-builder --publish never --linux",
    "build:linux:dev": "pnpm run prepare:dev && electron-vite build && electron-builder --publish never --linux"
  },
  "dependencies": {
    "@electron-toolkit/utils": "^4.0.0",
    "@types/plist": "^3.0.5",
    "adm-zip": "^0.5.16",
    "axios": "^1.13.5",
    "chokidar": "^5.0.0",
    "croner": "^9.1.0",
    "crypto-js": "^4.2.0",
    "express": "^5.2.1",
    "file-icon": "^6.0.0",
    "file-icon-info": "^1.1.1",
    "i18next": "^25.8.13",
    "iconv-lite": "^0.7.2",
    "js-yaml": "^4.1.1",
    "plist": "^3.1.0",
    "sysproxy-rs": "file:src\\native\\sysproxy",
    "validator": "^13.15.26",
    "webdav": "^5.9.0",
    "ws": "^8.19.0",
    "yaml": "^2.8.2"
  },
  "devDependencies": {
    "@dnd-kit/core": "^6.3.1",
    "@dnd-kit/sortable": "^10.0.0",
    "@dnd-kit/utilities": "^3.2.2",
    "@electron-toolkit/eslint-config-prettier": "^3.0.0",
    "@electron-toolkit/eslint-config-ts": "^3.1.0",
    "@electron-toolkit/tsconfig": "^2.0.0",
    "@heroui/react": "^2.8.9",
    "@tailwindcss/vite": "^4.2.1",
    "@types/adm-zip": "^0.5.7",
    "@types/crypto-js": "^4.2.2",
    "@types/express": "^5.0.6",
    "@types/node": "^25.3.0",
    "@types/pubsub-js": "^1.8.6",
    "@types/react": "^19.2.14",
    "@types/react-dom": "^19.2.3",
    "@types/validator": "^13.15.10",
    "@types/ws": "^8.18.1",
    "@typescript-eslint/eslint-plugin": "^8.56.1",
    "@typescript-eslint/parser": "^8.56.1",
    "@vitejs/plugin-react": "^5.1.4",
    "chart.js": "^4.5.1",
    "cron-validator": "^1.4.0",
    "dayjs": "^1.11.19",
    "driver.js": "^1.4.0",
    "electron": "37.10.0",
    "electron-builder": "26.0.12",
    "electron-vite": "4.0.1",
    "electron-window-state": "^5.0.3",
    "eslint": "9.39.2",
    "eslint-plugin-import": "^2.32.0",
    "eslint-plugin-react": "^7.37.5",
    "eslint-plugin-react-hooks": "^7.0.1",
    "form-data": "^4.0.5",
    "framer-motion": "12.23.26",
    "lodash": "^4.17.23",
    "meta-json-schema": "^1.19.20",
    "monaco-yaml": "^5.4.1",
    "nanoid": "^5.1.6",
    "next-themes": "^0.4.6",
    "postcss": "^8.5.6",
    "prettier": "^3.8.1",
    "pubsub-js": "^1.9.5",
    "react": "^19.2.4",
    "react-chartjs-2": "^5.3.1",
    "react-dom": "^19.2.4",
    "react-error-boundary": "^6.1.1",
    "react-i18next": "^16.5.4",
    "react-icons": "^5.5.0",
    "react-markdown": "^10.1.0",
    "react-monaco-editor": "^0.59.0",
    "react-router-dom": "^7.13.1",
    "react-virtuoso": "^4.18.1",
    "swr": "^2.4.0",
    "tailwindcss": "^4.2.1",
    "tar": "^7.5.9",
    "tsx": "^4.21.0",
    "types-pac": "^1.0.3",
    "typescript": "^5.9.3",
    "vite": "^7.3.1",
    "vite-plugin-monaco-editor": "^1.1.0"
  },
  "packageManager": "pnpm@10.27.0"
}


================================================
FILE: scripts/checksum.mjs
================================================
import { readFileSync, readdirSync, writeFileSync } from 'fs'
import { createHash } from 'crypto'
const files = readdirSync('dist')

for (const file of files) {
  for (const ext of process.argv.slice(2)) {
    if (file.endsWith(ext)) {
      const content = readFileSync(`dist/${file}`)
      const checksum = createHash('sha256').update(content, 'utf8').digest('hex')
      writeFileSync(`dist/${file}.sha256`, checksum)
    }
  }
}


================================================
FILE: scripts/cleanup-mac.sh
================================================
#!/bin/bash

echo "=== Clash Party Cleanup Tool ==="
echo "This script will remove all Clash Party related files and services."
read -p "Are you sure you want to continue? (y/N) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
    echo "Aborted."
    exit 1
fi

# Stop and unload services
echo "Stopping services..."
sudo launchctl unload /Library/LaunchDaemons/party.mihomo.helper.plist 2>/dev/null || true

# Remove files
echo "Removing files..."
sudo rm -f /Library/LaunchDaemons/party.mihomo.helper.plist
sudo rm -f /Library/PrivilegedHelperTools/party.mihomo.helper
sudo rm -rf "/Applications/Clash Party.app"
sudo rm -rf "/Applications/Clash\\ Party.app"
sudo rm -rf ~/Library/Application\ Support/mihomo-party
sudo rm -rf ~/Library/Caches/mihomo-party
sudo rm -f ~/Library/Preferences/party.mihomo.app.helper.plist
sudo rm -f ~/Library/Preferences/party.mihomo.app.plist

echo "Cleanup complete. Please restart your computer to complete the process."


================================================
FILE: scripts/copy-legacy-artifacts.mjs
================================================
import { readFileSync, readdirSync, writeFileSync, copyFileSync, existsSync } from 'fs'
import { join } from 'path'

/**
 * 复制打包产物并重命名为兼容旧版本的文件名
 * 将 clash-party 重命名为 mihomo-party,用于更新检测兼容性
 */

const distDir = 'dist'

if (!existsSync(distDir)) {
  console.log('❌ dist 目录不存在,请先执行打包命令')
  process.exit(1)
}

const files = readdirSync(distDir)
console.log('📦 开始处理打包产物...')

let copiedCount = 0

for (const file of files) {
  if (file.includes('clash-party') && !file.endsWith('.sha256')) {
    const newFileName = file.replace('clash-party', 'mihomo-party')
    const sourcePath = join(distDir, file)
    const targetPath = join(distDir, newFileName)

    try {
      copyFileSync(sourcePath, targetPath)
      console.log(`✅ 复制: ${file} -> ${newFileName}`)
      copiedCount++

      const sha256File = `${file}.sha256`
      const sha256Path = join(distDir, sha256File)

      if (existsSync(sha256Path)) {
        const newSha256File = `${newFileName}.sha256`
        const newSha256Path = join(distDir, newSha256File)

        const sha256Content = readFileSync(sha256Path, 'utf8')
        writeFileSync(newSha256Path, sha256Content)
        console.log(`✅ 复制校验文件: ${sha256File} -> ${newSha256File}`)
        copiedCount++
      }
    } catch (error) {
      console.error(`❌ 复制文件失败: ${file}`, error.message)
    }
  }
}

if (copiedCount > 0) {
  console.log(`🎉 成功复制 ${copiedCount} 个文件`)
  console.log('📋 现在 dist 目录包含以下文件:')

  const finalFiles = readdirSync(distDir).sort()
  finalFiles.forEach((file) => {
    if (file.includes('clash-party') || file.includes('mihomo-party')) {
      const isLegacy = file.includes('mihomo-party')
      console.log(`   ${isLegacy ? '🔄' : '📦'} ${file}`)
    }
  })

  console.log('   📦 = 原始文件 (clash-party)')
  console.log('   🔄 = 兼容文件 (mihomo-party)')
} else {
  console.log('ℹ️  没有找到需要复制的 clash-party 文件')
}


================================================
FILE: scripts/prepare.mjs
================================================
import fs from 'fs'
import AdmZip from 'adm-zip'
import path from 'path'
import zlib from 'zlib'
import { extract } from 'tar'
import { execSync } from 'child_process'

const cwd = process.cwd()
const TEMP_DIR = path.join(cwd, 'node_modules/.temp')
let arch = process.arch
const platform = process.platform
if (process.argv.slice(2).length !== 0) {
  arch = process.argv.slice(2)[0].replace('--', '')
}

/* ======= mihomo alpha======= */
const MIHOMO_ALPHA_VERSION_URL =
  'https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha/version.txt'
const MIHOMO_ALPHA_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download/Prerelease-Alpha`
let MIHOMO_ALPHA_VERSION

const MIHOMO_ALPHA_MAP = {
  'win32-x64': 'mihomo-windows-amd64-compatible',
  'win32-ia32': 'mihomo-windows-386',
  'win32-arm64': 'mihomo-windows-arm64',
  'darwin-x64': 'mihomo-darwin-amd64-compatible',
  'darwin-arm64': 'mihomo-darwin-arm64',
  'linux-x64': 'mihomo-linux-amd64-compatible',
  'linux-arm64': 'mihomo-linux-arm64'
}

// Fetch the latest alpha release version from the version.txt file
async function getLatestAlphaVersion() {
  try {
    const response = await fetch(MIHOMO_ALPHA_VERSION_URL, {
      method: 'GET'
    })
    let v = await response.text()
    MIHOMO_ALPHA_VERSION = v.trim() // Trim to remove extra whitespaces
    console.log(`Latest alpha version: ${MIHOMO_ALPHA_VERSION}`)
  } catch (error) {
    console.error('Error fetching latest alpha version:', error.message)
    process.exit(1)
  }
}

/* ======= mihomo smart ======= */
const MIHOMO_SMART_VERSION_URL =
  'https://github.com/vernesong/mihomo/releases/download/Prerelease-Alpha/version.txt'
const MIHOMO_SMART_URL_PREFIX = `https://github.com/vernesong/mihomo/releases/download/Prerelease-Alpha`
let MIHOMO_SMART_VERSION

const MIHOMO_SMART_MAP = {
  'win32-x64': 'mihomo-windows-amd64-v2-go120',
  'win32-ia32': 'mihomo-windows-386-go120',
  'win32-arm64': 'mihomo-windows-arm64',
  'darwin-x64': 'mihomo-darwin-amd64-v2-go120',
  'darwin-arm64': 'mihomo-darwin-arm64',
  'linux-x64': 'mihomo-linux-amd64-v2-go120',
  'linux-arm64': 'mihomo-linux-arm64'
}

async function getLatestSmartVersion() {
  try {
    const response = await fetch(MIHOMO_SMART_VERSION_URL, {
      method: 'GET'
    })
    let v = await response.text()
    MIHOMO_SMART_VERSION = v.trim() // Trim to remove extra whitespaces
    console.log(`Latest smart version: ${MIHOMO_SMART_VERSION}`)
  } catch (error) {
    console.error('Error fetching latest smart version:', error.message)
    process.exit(1)
  }
}

/* ======= mihomo release ======= */
const MIHOMO_VERSION_URL =
  'https://github.com/MetaCubeX/mihomo/releases/latest/download/version.txt'
const MIHOMO_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/download`
let MIHOMO_VERSION

const MIHOMO_MAP = {
  'win32-x64': 'mihomo-windows-amd64-compatible',
  'win32-ia32': 'mihomo-windows-386',
  'win32-arm64': 'mihomo-windows-arm64',
  'darwin-x64': 'mihomo-darwin-amd64-compatible',
  'darwin-arm64': 'mihomo-darwin-arm64',
  'linux-x64': 'mihomo-linux-amd64-compatible',
  'linux-arm64': 'mihomo-linux-arm64'
}

// Fetch the latest release version from the version.txt file
async function getLatestReleaseVersion() {
  try {
    const response = await fetch(MIHOMO_VERSION_URL, {
      method: 'GET'
    })
    let v = await response.text()
    MIHOMO_VERSION = v.trim() // Trim to remove extra whitespaces
    console.log(`Latest release version: ${MIHOMO_VERSION}`)
  } catch (error) {
    console.error('Error fetching latest release version:', error.message)
    process.exit(1)
  }
}

/*
 * check available
 */
if (!MIHOMO_MAP[`${platform}-${arch}`]) {
  throw new Error(`unsupported platform "${platform}-${arch}"`)
}

if (!MIHOMO_ALPHA_MAP[`${platform}-${arch}`]) {
  throw new Error(`unsupported platform "${platform}-${arch}"`)
}

if (!MIHOMO_SMART_MAP[`${platform}-${arch}`]) {
  throw new Error(`unsupported platform "${platform}-${arch}"`)
}

/**
 * core info
 */
function MihomoAlpha() {
  const name = MIHOMO_ALPHA_MAP[`${platform}-${arch}`]
  const isWin = platform === 'win32'
  const urlExt = isWin ? 'zip' : 'gz'
  const downloadURL = `${MIHOMO_ALPHA_URL_PREFIX}/${name}-${MIHOMO_ALPHA_VERSION}.${urlExt}`
  const exeFile = `${name}${isWin ? '.exe' : ''}`
  const zipFile = `${name}-${MIHOMO_ALPHA_VERSION}.${urlExt}`

  return {
    name: 'mihomo-alpha',
    targetFile: `mihomo-alpha${isWin ? '.exe' : ''}`,
    exeFile,
    zipFile,
    downloadURL
  }
}

function mihomo() {
  const name = MIHOMO_MAP[`${platform}-${arch}`]
  const isWin = platform === 'win32'
  const urlExt = isWin ? 'zip' : 'gz'
  const downloadURL = `${MIHOMO_URL_PREFIX}/${MIHOMO_VERSION}/${name}-${MIHOMO_VERSION}.${urlExt}`
  const exeFile = `${name}${isWin ? '.exe' : ''}`
  const zipFile = `${name}-${MIHOMO_VERSION}.${urlExt}`

  return {
    name: 'mihomo',
    targetFile: `mihomo${isWin ? '.exe' : ''}`,
    exeFile,
    zipFile,
    downloadURL
  }
}

function mihomoSmart() {
  const name = MIHOMO_SMART_MAP[`${platform}-${arch}`]
  const isWin = platform === 'win32'
  const urlExt = isWin ? 'zip' : 'gz'
  const downloadURL = `${MIHOMO_SMART_URL_PREFIX}/${name}-${MIHOMO_SMART_VERSION}.${urlExt}`
  const exeFile = `${name}${isWin ? '.exe' : ''}`
  const zipFile = `${name}-${MIHOMO_SMART_VERSION}.${urlExt}`

  return {
    name: 'mihomo-smart',
    targetFile: `mihomo-smart${isWin ? '.exe' : ''}`,
    exeFile,
    zipFile,
    downloadURL
  }
}
/**
 * download sidecar and rename
 */
async function resolveSidecar(binInfo) {
  const { name, targetFile, zipFile, exeFile, downloadURL } = binInfo

  const sidecarDir = path.join(cwd, 'extra', 'sidecar')
  const sidecarPath = path.join(sidecarDir, targetFile)

  fs.mkdirSync(sidecarDir, { recursive: true })
  if (fs.existsSync(sidecarPath)) {
    fs.rmSync(sidecarPath)
  }
  const tempDir = path.join(TEMP_DIR, name)
  const tempZip = path.join(tempDir, zipFile)
  const tempExe = path.join(tempDir, exeFile)

  fs.mkdirSync(tempDir, { recursive: true })
  try {
    if (!fs.existsSync(tempZip)) {
      await downloadFile(downloadURL, tempZip)
    }

    if (zipFile.endsWith('.zip')) {
      const zip = new AdmZip(tempZip)
      zip.getEntries().forEach((entry) => {
        console.log(`[DEBUG]: "${name}" entry name`, entry.entryName)
      })
      zip.extractAllTo(tempDir, true)
      fs.renameSync(tempExe, sidecarPath)
      console.log(`[INFO]: "${name}" unzip finished`)
    } else if (zipFile.endsWith('.tgz')) {
      // tgz
      fs.mkdirSync(tempDir, { recursive: true })
      await extract({
        cwd: tempDir,
        file: tempZip
      })
      const files = fs.readdirSync(tempDir)
      console.log(`[DEBUG]: "${name}" files in tempDir:`, files)
      const extractedFile = files.find((file) => file.startsWith('虚空终端-'))
      if (extractedFile) {
        const extractedFilePath = path.join(tempDir, extractedFile)
        fs.renameSync(extractedFilePath, sidecarPath)
        console.log(`[INFO]: "${name}" file renamed to "${sidecarPath}"`)
        execSync(`chmod 755 ${sidecarPath}`)
        console.log(`[INFO]: "${name}" chmod binary finished`)
      } else {
        throw new Error(`Expected file not found in ${tempDir}`)
      }
    } else {
      // gz
      const readStream = fs.createReadStream(tempZip)
      const writeStream = fs.createWriteStream(sidecarPath)
      await new Promise((resolve, reject) => {
        const onError = (error) => {
          console.error(`[ERROR]: "${name}" gz failed:`, error.message)
          reject(error)
        }
        readStream
          .pipe(zlib.createGunzip().on('error', onError))
          .pipe(writeStream)
          .on('finish', () => {
            console.log(`[INFO]: "${name}" gunzip finished`)
            execSync(`chmod 755 ${sidecarPath}`)
            console.log(`[INFO]: "${name}" chmod binary finished`)
            resolve()
          })
          .on('error', onError)
      })
    }
  } catch (err) {
    // 需要删除文件
    fs.rmSync(sidecarPath)
    throw err
  } finally {
    fs.rmSync(tempDir, { recursive: true })
  }
}

/**
 * download the file to the extra dir
 */
async function resolveResource(binInfo) {
  const { file, downloadURL } = binInfo

  const resDir = path.join(cwd, 'extra', 'files')
  const targetPath = path.join(resDir, file)

  if (fs.existsSync(targetPath)) {
    fs.rmSync(targetPath)
  }

  fs.mkdirSync(resDir, { recursive: true })
  await downloadFile(downloadURL, targetPath)

  console.log(`[INFO]: ${file} finished`)
}

/**
 * download file and save to `path`
 */
async function downloadFile(url, path) {
  const response = await fetch(url, {
    method: 'GET',
    headers: { 'Content-Type': 'application/octet-stream' }
  })
  const buffer = await response.arrayBuffer()
  fs.writeFileSync(path, new Uint8Array(buffer))

  console.log(`[INFO]: download finished "${url}"`)
}

const resolveMmdb = () =>
  resolveResource({
    file: 'country.mmdb',
    downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/country-lite.mmdb`
  })
const resolveMetadb = () =>
  resolveResource({
    file: 'geoip.metadb',
    downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.metadb`
  })
const resolveGeosite = () =>
  resolveResource({
    file: 'geosite.dat',
    downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geosite.dat`
  })
const resolveGeoIP = () =>
  resolveResource({
    file: 'geoip.dat',
    downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/geoip.dat`
  })
const resolveASN = () =>
  resolveResource({
    file: 'ASN.mmdb',
    downloadURL: `https://github.com/MetaCubeX/meta-rules-dat/releases/download/latest/GeoLite2-ASN.mmdb`
  })
const resolveEnableLoopback = () =>
  resolveResource({
    file: 'enableLoopback.exe',
    downloadURL: `https://github.com/Kuingsmile/uwp-tool/releases/download/latest/enableLoopback.exe`
  })
/* ======= sysproxy-rs ======= */
const SYSPROXY_RS_VERSION = 'v0.1.0'
const SYSPROXY_RS_URL_PREFIX = `https://github.com/mihomo-party-org/sysproxy-rs-opti/releases/download/${SYSPROXY_RS_VERSION}`

function getSysproxyNodeName() {
  // 检测是否为 musl 系统(与 src/native/sysproxy/index.js 保持一致)
  const isMusl = (() => {
    if (platform !== 'linux') return false
    try {
      const output = execSync('ldd --version 2>&1 || true').toString()
      return output.includes('musl')
    } catch {
      return false
    }
  })()

  const isWin7Build = process.env.LEGACY_BUILD === 'true'

  switch (platform) {
    case 'win32':
      if (arch === 'x64')
        return isWin7Build ? 'sysproxy.win32-x64-msvc-win7.node' : 'sysproxy.win32-x64-msvc.node'
      if (arch === 'arm64') return 'sysproxy.win32-arm64-msvc.node'
      if (arch === 'ia32')
        return isWin7Build ? 'sysproxy.win32-ia32-msvc-win7.node' : 'sysproxy.win32-ia32-msvc.node'
      break
    case 'darwin':
      if (arch === 'x64') return 'sysproxy.darwin-x64.node'
      if (arch === 'arm64') return 'sysproxy.darwin-arm64.node'
      break
    case 'linux':
      if (isMusl) {
        if (arch === 'x64') return 'sysproxy.linux-x64-musl.node'
        if (arch === 'arm64') return 'sysproxy.linux-arm64-musl.node'
      } else {
        if (arch === 'x64') return 'sysproxy.linux-x64-gnu.node'
        if (arch === 'arm64') return 'sysproxy.linux-arm64-gnu.node'
      }
      break
  }
  throw new Error(`Unsupported platform for sysproxy-rs: ${platform}-${arch}`)
}

const resolveSysproxy = async () => {
  const nodeName = getSysproxyNodeName()
  const sidecarDir = path.join(cwd, 'extra', 'sidecar')
  const targetPath = path.join(sidecarDir, nodeName)

  fs.mkdirSync(sidecarDir, { recursive: true })

  // 清理其他平台的 .node 文件
  const files = fs.readdirSync(sidecarDir)
  for (const file of files) {
    if (file.endsWith('.node') && file !== nodeName) {
      fs.rmSync(path.join(sidecarDir, file))
      console.log(`[INFO]: removed ${file}`)
    }
  }

  if (fs.existsSync(targetPath)) {
    fs.rmSync(targetPath)
  }

  await downloadFile(`${SYSPROXY_RS_URL_PREFIX}/${nodeName}`, targetPath)
  console.log(`[INFO]: ${nodeName} finished`)
}

const resolveMonitor = async () => {
  const tempDir = path.join(TEMP_DIR, 'TrafficMonitor')
  const tempZip = path.join(tempDir, `${arch}.zip`)
  if (!fs.existsSync(tempDir)) {
    fs.mkdirSync(tempDir, { recursive: true })
  }
  await downloadFile(
    `https://github.com/mihomo-party-org/mihomo-party-run/releases/download/monitor/${arch}.zip`,
    tempZip
  )
  const zip = new AdmZip(tempZip)
  const resDir = path.join(cwd, 'extra', 'files')
  const targetPath = path.join(resDir, 'TrafficMonitor')
  if (fs.existsSync(targetPath)) {
    fs.rmSync(targetPath, { recursive: true })
  }
  zip.extractAllTo(targetPath, true)

  console.log(`[INFO]: TrafficMonitor finished`)
}

const resolve7zip = () =>
  resolveResource({
    file: '7za.exe',
    downloadURL: `https://github.com/develar/7zip-bin/raw/master/win/${arch}/7za.exe`
  })
const resolveSubstore = () =>
  resolveResource({
    file: 'sub-store.bundle.cjs',
    downloadURL:
      'https://github.com/sub-store-org/Sub-Store/releases/latest/download/sub-store.bundle.js'
  })
const resolveHelper = () =>
  resolveResource({
    file: 'party.mihomo.helper',
    downloadURL: `https://github.com/mihomo-party-org/mihomo-party-helper/releases/download/${arch}/party.mihomo.helper`
  })
const resolveSubstoreFrontend = async () => {
  const tempDir = path.join(TEMP_DIR, 'substore-frontend')
  const tempZip = path.join(tempDir, 'dist.zip')
  if (!fs.existsSync(tempDir)) {
    fs.mkdirSync(tempDir, { recursive: true })
  }
  await downloadFile(
    'https://github.com/sub-store-org/Sub-Store-Front-End/releases/latest/download/dist.zip',
    tempZip
  )
  const zip = new AdmZip(tempZip)
  const resDir = path.join(cwd, 'extra', 'files')
  const targetPath = path.join(resDir, 'sub-store-frontend')
  if (fs.existsSync(targetPath)) {
    fs.rmSync(targetPath, { recursive: true })
  }
  zip.extractAllTo(resDir, true)
  fs.renameSync(path.join(resDir, 'dist'), targetPath)

  console.log(`[INFO]: sub-store-frontend finished`)
}
const resolveFont = async () => {
  const targetPath = path.join(cwd, 'src', 'renderer', 'src', 'assets', 'NotoColorEmoji.ttf')

  if (fs.existsSync(targetPath)) {
    return
  }
  await downloadFile(
    'https://github.com/googlefonts/noto-emoji/raw/main/fonts/NotoColorEmoji.ttf',
    targetPath
  )

  console.log(`[INFO]: NotoColorEmoji.ttf finished`)
}

const tasks = [
  {
    name: 'mihomo-alpha',
    func: () => getLatestAlphaVersion().then(() => resolveSidecar(MihomoAlpha())),
    retry: 5
  },
  {
    name: 'mihomo',
    func: () => getLatestReleaseVersion().then(() => resolveSidecar(mihomo())),
    retry: 5
  },
  {
    name: 'mihomo-smart',
    func: () => getLatestSmartVersion().then(() => resolveSidecar(mihomoSmart())),
    retry: 5
  },
  { name: 'mmdb', func: resolveMmdb, retry: 5 },
  { name: 'metadb', func: resolveMetadb, retry: 5 },
  { name: 'geosite', func: resolveGeosite, retry: 5 },
  { name: 'geoip', func: resolveGeoIP, retry: 5 },
  { name: 'asn', func: resolveASN, retry: 5 },
  {
    name: 'font',
    func: resolveFont,
    retry: 5
  },
  {
    name: 'enableLoopback',
    func: resolveEnableLoopback,
    retry: 5,
    winOnly: true
  },
  {
    name: 'sysproxy',
    func: resolveSysproxy,
    retry: 5
  },
  {
    name: 'monitor',
    func: resolveMonitor,
    retry: 5,
    winOnly: true
  },
  {
    name: 'substore',
    func: resolveSubstore,
    retry: 5
  },
  {
    name: 'substorefrontend',
    func: resolveSubstoreFrontend,
    retry: 5
  },
  {
    name: '7zip',
    func: resolve7zip,
    retry: 5,
    winOnly: true
  },
  {
    name: 'helper',
    func: resolveHelper,
    retry: 5,
    darwinOnly: true
  }
]

async function runTask() {
  const task = tasks.shift()
  if (!task) return
  if (task.winOnly && platform !== 'win32') return runTask()
  if (task.linuxOnly && platform !== 'linux') return runTask()
  if (task.unixOnly && platform === 'win32') return runTask()
  if (task.darwinOnly && platform !== 'darwin') return runTask()

  for (let i = 0; i < task.retry; i++) {
    try {
      await task.func()
      break
    } catch (err) {
      console.error(`[ERROR]: task::${task.name} try ${i} ==`, err.message)
      if (i === task.retry - 1) {
        if (task.optional) {
          console.log(`[WARN]: Optional task::${task.name} failed, skipping...`)
          break
        } else {
          throw err
        }
      }
    }
  }
  return runTask()
}

runTask()
runTask()


================================================
FILE: scripts/telegram.mjs
================================================
import axios from 'axios'
import { readFileSync } from 'fs'
import {
  getProcessedVersion,
  isDevBuild,
  getDownloadUrl,
  generateDownloadLinksMarkdown,
  getGitCommitHash
} from './version-utils.mjs'

const chat_id = '@MihomoPartyChannel'
const changelog = readFileSync('changelog.md', 'utf-8')

// 获取处理后的版本号
const version = getProcessedVersion()
const releaseType = process.env.RELEASE_TYPE || process.argv[2] || 'release'
const isDevRelease = releaseType === 'dev' || isDevBuild()

function convertMarkdownToTelegramHTML(content) {
  return content
    .split('\n')
    .map((line) => {
      if (line.trim().length === 0) {
        return ''
      } else if (line.startsWith('## ')) {
        return `<b>${line.replace('## ', '')}</b>`
      } else if (line.startsWith('### ')) {
        return `<b>${line.replace('### ', '')}</b>`
      } else if (line.startsWith('#### ')) {
        return `<b>${line.replace('#### ', '')}</b>`
      } else {
        let processedLine = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, (match, text, url) => {
          const encodedUrl = encodeURI(url)
          return `<a href="${encodedUrl}">${text}</a>`
        })
        processedLine = processedLine.replace(/\*\*([^*]+)\*\*/g, '<b>$1</b>')
        return processedLine
      }
    })
    .join('\n')
}

let content = ''

if (isDevRelease) {
  // 版本号中提取commit hash
  const shortCommitSha = getGitCommitHash(true)
  const commitSha = getGitCommitHash(false)

  content = `<b>🚧 <a href="https://github.com/mihomo-party-org/clash-party/releases/tag/dev">Clash Party Dev Build</a> 开发版本发布</b>\n\n`
  content += `<b>基于版本:</b> ${version}\n`
  content += `<b>提交哈希:</b> <a href="https://github.com/mihomo-party-org/clash-party/commit/${commitSha}">${shortCommitSha}</a>\n\n`
  content += `<b>更新日志:</b>\n`
  content += convertMarkdownToTelegramHTML(changelog)
  content += '\n\n<b>⚠️ 注意:这是开发版本,可能存在不稳定性,仅供测试使用</b>\n'
} else {
  // 正式版本通知
  content = `<b>🌟 <a href="https://github.com/mihomo-party-org/clash-party/releases/tag/v${version}">Clash Party v${version}</a> 正式发布</b>\n\n`
  content += convertMarkdownToTelegramHTML(changelog)
}

// 构建下载链接
const downloadUrl = getDownloadUrl(isDevRelease, version)

const downloadLinksMarkdown = generateDownloadLinksMarkdown(downloadUrl, version)
content += convertMarkdownToTelegramHTML(downloadLinksMarkdown)

await axios.post(`https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`, {
  chat_id,
  text: content,
  link_preview_options: {
    is_disabled: false,
    url: 'https://github.com/mihomo-party-org/clash-party',
    prefer_large_media: true
  },
  parse_mode: 'HTML'
})

console.log(`${isDevRelease ? '开发版本' : '正式版本'}Telegram 通知发送成功`)


================================================
FILE: scripts/update-version.mjs
================================================
import { readFileSync, writeFileSync } from 'fs'
import { getProcessedVersion, isDevBuild } from './version-utils.mjs'

// 更新package.json中的版本号
function updatePackageVersion() {
  try {
    const packagePath = 'package.json'
    const packageContent = readFileSync(packagePath, 'utf-8')
    const packageData = JSON.parse(packageContent)

    // 获取处理后的版本号
    const newVersion = getProcessedVersion()

    console.log(`当前版本: ${packageData.version}`)
    console.log(`${isDevBuild() ? 'Dev构建' : '正式构建'} - 新版本: ${newVersion}`)

    packageData.version = newVersion

    // 写回package.json
    writeFileSync(packagePath, JSON.stringify(packageData, null, 2) + '\n')

    console.log(`✅ package.json版本号已更新为: ${newVersion}`)
  } catch (error) {
    console.error('❌ 更新package.json版本号失败:', error.message)
    process.exit(1)
  }
}

updatePackageVersion()

export { updatePackageVersion }


================================================
FILE: scripts/updater.mjs
================================================
import yaml from 'yaml'
import { readFileSync, writeFileSync } from 'fs'
import {
  getProcessedVersion,
  isDevBuild,
  getDownloadUrl,
  generateDownloadLinksMarkdown
} from './version-utils.mjs'

let changelog = readFileSync('changelog.md', 'utf-8')

// 获取处理后的版本号
const version = getProcessedVersion()
const isDev = isDevBuild()
const downloadUrl = getDownloadUrl(isDev, version)

const latest = {
  version,
  changelog
}

// 使用统一的下载链接生成函数
changelog += generateDownloadLinksMarkdown(downloadUrl, version)

changelog +=
  '\n\n### 机场推荐:\n- 高性能海外机场,稳定首选:[https://狗狗加速.com](https://party.dginv.click/#/register?code=ARdo0mXx)'

writeFileSync('latest.yml', yaml.stringify(latest))
writeFileSync('changelog.md', changelog)


================================================
FILE: scripts/version-utils.mjs
================================================
import { execSync } from 'child_process'
import { readFileSync } from 'fs'

// 获取Git commit hash
export function getGitCommitHash(short = true) {
  try {
    const command = short ? 'git rev-parse --short HEAD' : 'git rev-parse HEAD'
    return execSync(command, { encoding: 'utf-8' }).trim()
  } catch (error) {
    console.warn('Failed to get git commit hash:', error.message)
    return 'unknown'
  }
}

// 获取当前月份日期
export function getCurrentMonthDate() {
  const now = new Date()
  const month = String(now.getMonth() + 1).padStart(2, '0')
  const day = String(now.getDate()).padStart(2, '0')
  return `${month}${day}`
}

// 从package.json读取基础版本号
export function getBaseVersion() {
  try {
    const pkg = readFileSync('package.json', 'utf-8')
    const { version } = JSON.parse(pkg)
    // 移除dev版本格式后缀
    return version.replace(/-d\d{2,4}\.[a-f0-9]{7}$/, '')
  } catch (error) {
    console.error('Failed to read package.json:', error.message)
    return '1.0.0'
  }
}

// 生成dev版本号
export function getDevVersion() {
  const baseVersion = getBaseVersion()
  const monthDate = getCurrentMonthDate()
  const commitHash = getGitCommitHash(true)

  return `${baseVersion}-d${monthDate}.${commitHash}`
}

// 检查当前环境是否为dev构建
export function isDevBuild() {
  return (
    process.env.NODE_ENV === 'development' ||
    process.argv.includes('--dev') ||
    process.env.GITHUB_EVENT_NAME === 'workflow_dispatch'
  )
}

// 获取处理后的版本号
export function getProcessedVersion() {
  if (isDevBuild()) {
    return getDevVersion()
  } else {
    return getBaseVersion()
  }
}

// 生成下载URL
export function getDownloadUrl(isDev, version) {
  if (isDev) {
    return 'https://github.com/mihomo-party-org/clash-party/releases/download/dev'
  } else {
    return `https://github.com/mihomo-party-org/clash-party/releases/download/v${version}`
  }
}

export function generateDownloadLinksMarkdown(downloadUrl, version) {
  let links = '\n### 下载地址:\n\n#### Windows10/11:\n\n'
  links += `- 安装版:[64位](${downloadUrl}/clash-party-windows-${version}-x64-setup.exe) | [32位](${downloadUrl}/clash-party-windows-${version}-ia32-setup.exe) | [ARM64](${downloadUrl}/clash-party-windows-${version}-arm64-setup.exe)\n\n`
  links += `- 便携版:[64位](${downloadUrl}/clash-party-windows-${version}-x64-portable.7z) | [32位](${downloadUrl}/clash-party-windows-${version}-ia32-portable.7z) | [ARM64](${downloadUrl}/clash-party-windows-${version}-arm64-portable.7z)\n\n`
  links += '\n#### Windows7/8:\n\n'
  links += `- 安装版:[64位](${downloadUrl}/clash-party-win7-${version}-x64-setup.exe) | [32位](${downloadUrl}/clash-party-win7-${version}-ia32-setup.exe)\n\n`
  links += `- 便携版:[64位](${downloadUrl}/clash-party-win7-${version}-x64-portable.7z) | [32位](${downloadUrl}/clash-party-win7-${version}-ia32-portable.7z)\n\n`
  links += '\n#### macOS 11+:\n\n'
  links += `- PKG:[Intel](${downloadUrl}/clash-party-macos-${version}-x64.pkg) | [Apple Silicon](${downloadUrl}/clash-party-macos-${version}-arm64.pkg)\n\n`
  links += '\n#### macOS 10.15+:\n\n'
  links += `- PKG:[Intel](${downloadUrl}/clash-party-catalina-${version}-x64.pkg) | [Apple Silicon](${downloadUrl}/clash-party-catalina-${version}-arm64.pkg)\n\n`
  links += '\n#### Linux:\n\n'
  links += `- DEB:[64位](${downloadUrl}/clash-party-linux-${version}-amd64.deb) | [ARM64](${downloadUrl}/clash-party-linux-${version}-arm64.deb)\n\n`
  links += `- RPM:[64位](${downloadUrl}/clash-party-linux-${version}-x86_64.rpm) | [ARM64](${downloadUrl}/clash-party-linux-${version}-aarch64.rpm)`

  return links
}


================================================
FILE: src/main/config/app.ts
================================================
import { readFile, writeFile } from 'fs/promises'
import { appConfigPath } from '../utils/dirs'
import { parse, stringify } from '../utils/yaml'
import { deepMerge } from '../utils/merge'
import { defaultConfig } from '../utils/template'

let appConfig: IAppConfig // config.yaml
let appConfigWriteQueue: Promise<void> = Promise.resolve()

export async function getAppConfig(force = false): Promise<IAppConfig> {
  if (force || !appConfig) {
    appConfigWriteQueue = appConfigWriteQueue.then(async () => {
      const data = await readFile(appConfigPath(), 'utf-8')
      const parsedConfig = parse(data)
      const mergedConfig = deepMerge({ ...defaultConfig }, parsedConfig || {})
      if (JSON.stringify(mergedConfig) !== JSON.stringify(parsedConfig)) {
        await writeFile(appConfigPath(), stringify(mergedConfig))
      }
      appConfig = mergedConfig
    })
    await appConfigWriteQueue
  }
  if (typeof appConfig !== 'object') appConfig = defaultConfig
  return appConfig
}

export async function patchAppConfig(patch: Partial<IAppConfig>): Promise<void> {
  appConfigWriteQueue = appConfigWriteQueue.then(async () => {
    if (patch.nameserverPolicy) {
      appConfig.nameserverPolicy = patch.nameserverPolicy
    }
    appConfig = deepMerge(appConfig, patch)
    await writeFile(appConfigPath(), stringify(appConfig))
  })
  await appConfigWriteQueue
}


================================================
FILE: src/main/config/controledMihomo.ts
================================================
import { readFile, writeFile } from 'fs/promises'
import { existsSync } from 'fs'
import { controledMihomoConfigPath } from '../utils/dirs'
import { parse, stringify } from '../utils/yaml'
import { generateProfile } from '../core/factory'
import { defaultControledMihomoConfig } from '../utils/template'
import { deepMerge } from '../utils/merge'
import { createLogger } from '../utils/logger'
import { getAppConfig } from './app'

const controledMihomoLogger = createLogger('ControledMihomo')

let controledMihomoConfig: Partial<IMihomoConfig> // mihomo.yaml
let controledMihomoWriteQueue: Promise<void> = Promise.resolve()

export async function getControledMihomoConfig(force = false): Promise<Partial<IMihomoConfig>> {
  if (force || !controledMihomoConfig) {
    if (existsSync(controledMihomoConfigPath())) {
      const data = await readFile(controledMihomoConfigPath(), 'utf-8')
      controledMihomoConfig = parse(data) || defaultControledMihomoConfig
    } else {
      controledMihomoConfig = defaultControledMihomoConfig
      try {
        await writeFile(
          controledMihomoConfigPath(),
          stringify(defaultControledMihomoConfig),
          'utf-8'
        )
      } catch (error) {
        controledMihomoLogger.error('Failed to create mihomo.yaml file', error)
      }
    }

    // 确保配置包含所有必要的默认字段,处理升级场景
    controledMihomoConfig = deepMerge(defaultControledMihomoConfig, controledMihomoConfig)

    // 清理端口字段中的 NaN 值,恢复为默认值
    const portFields = ['mixed-port', 'socks-port', 'port', 'redir-port', 'tproxy-port'] as const
    for (const field of portFields) {
      if (
        typeof controledMihomoConfig[field] !== 'number' ||
        Number.isNaN(controledMihomoConfig[field])
      ) {
        controledMihomoConfig[field] = defaultControledMihomoConfig[field]
      }
    }
  }
  if (typeof controledMihomoConfig !== 'object')
    controledMihomoConfig = defaultControledMihomoConfig
  return controledMihomoConfig
}

export async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>): Promise<void> {
  controledMihomoWriteQueue = controledMihomoWriteQueue.then(async () => {
    const { controlDns = true, controlSniff = true } = await getAppConfig()

    // 过滤端口字段中的 NaN 值,防止写入无效配置
    const portFields = ['mixed-port', 'socks-port', 'port', 'redir-port', 'tproxy-port'] as const
    for (const field of portFields) {
      if (field in patch && (typeof patch[field] !== 'number' || Number.isNaN(patch[field]))) {
        delete patch[field]
      }
    }

    if (patch.hosts) {
      controledMihomoConfig.hosts = patch.hosts
    }
    if (patch.dns?.['nameserver-policy']) {
      controledMihomoConfig.dns = controledMihomoConfig.dns || {}
      controledMihomoConfig.dns['nameserver-policy'] = patch.dns['nameserver-policy']
    }
    controledMihomoConfig = deepMerge(controledMihomoConfig, patch)

    // 从不接管状态恢复
    if (controlDns) {
      // 确保 DNS 配置包含所有必要的默认字段,特别是新增的 fallback 等
      controledMihomoConfig.dns = deepMerge(
        defaultControledMihomoConfig.dns || {},
        controledMihomoConfig.dns || {}
      )
    }
    if (controlSniff && !controledMihomoConfig.sniffer) {
      controledMihomoConfig.sniffer = defaultControledMihomoConfig.sniffer
    }

    await generateProfile()
    await writeFile(controledMihomoConfigPath(), stringify(controledMihomoConfig), 'utf-8')
  })
  await controledMihomoWriteQueue
}


================================================
FILE: src/main/config/index.ts
================================================
export { getAppConfig, patchAppConfig } from './app'
export { getControledMihomoConfig, patchControledMihomoConfig } from './controledMihomo'
export {
  getProfile,
  getCurrentProfileItem,
  getProfileItem,
  getProfileConfig,
  getFileStr,
  setFileStr,
  setProfileConfig,
  addProfileItem,
  removeProfileItem,
  createProfile,
  getProfileStr,
  setProfileStr,
  changeCurrentProfile,
  updateProfileItem,
  convertMrsRuleset
} from './profile'
export {
  getOverrideConfig,
  setOverrideConfig,
  getOverrideItem,
  addOverrideItem,
  removeOverrideItem,
  createOverride,
  getOverride,
  setOverride,
  updateOverrideItem
} from './override'
export {
  createSmartOverride,
  removeSmartOverride,
  manageSmartOverride,
  isSmartOverrideExists
} from './smartOverride'


================================================
FILE: src/main/config/override.ts
================================================
import { readFile, writeFile, rm } from 'fs/promises'
import { existsSync } from 'fs'
import { overrideConfigPath, overridePath } from '../utils/dirs'
import * as chromeRequest from '../utils/chromeRequest'
import { parse, stringify } from '../utils/yaml'
import { getControledMihomoConfig } from './controledMihomo'

let overrideConfig: IOverrideConfig // override.yaml
let overrideConfigWriteQueue: Promise<void> = Promise.resolve()

export async function getOverrideConfig(force = false): Promise<IOverrideConfig> {
  if (force || !overrideConfig) {
    const data = await readFile(overrideConfigPath(), 'utf-8')
    overrideConfig = parse(data) || { items: [] }
  }
  if (typeof overrideConfig !== 'object') overrideConfig = { items: [] }
  if (!Array.isArray(overrideConfig.items)) overrideConfig.items = []
  return overrideConfig
}

export async function setOverrideConfig(config: IOverrideConfig): Promise<void> {
  overrideConfigWriteQueue = overrideConfigWriteQueue.then(async () => {
    overrideConfig = config
    await writeFile(overrideConfigPath(), stringify(overrideConfig), 'utf-8')
  })
  await overrideConfigWriteQueue
}

export async function getOverrideItem(id: string | undefined): Promise<IOverrideItem | undefined> {
  const { items } = await getOverrideConfig()
  return items.find((item) => item.id === id)
}

export async function updateOverrideItem(item: IOverrideItem): Promise<void> {
  const config = await getOverrideConfig()
  const index = config.items.findIndex((i) => i.id === item.id)
  if (index === -1) {
    throw new Error('Override not found')
  }
  config.items[index] = item
  await setOverrideConfig(config)
}

export async function addOverrideItem(item: Partial<IOverrideItem>): Promise<void> {
  const config = await getOverrideConfig()
  const newItem = await createOverride(item)
  if (await getOverrideItem(item.id)) {
    await updateOverrideItem(newItem)
  } else {
    config.items.push(newItem)
    await setOverrideConfig(config)
  }
}

export async function removeOverrideItem(id: string): Promise<void> {
  const config = await getOverrideConfig()
  const item = await getOverrideItem(id)
  if (!item) return
  config.items = config.items?.filter((i) => i.id !== id)
  await setOverrideConfig(config)
  if (existsSync(overridePath(id, item.ext))) {
    await rm(overridePath(id, item.ext))
  }
}

export async function createOverride(item: Partial<IOverrideItem>): Promise<IOverrideItem> {
  const id = item.id || new Date().getTime().toString(16)
  const newItem = {
    id,
    name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
    type: item.type,
    ext: item.ext || 'js',
    url: item.url,
    global: item.global || false,
    updated: new Date().getTime()
  } as IOverrideItem
  switch (newItem.type) {
    case 'remote': {
      const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
      if (!item.url) throw new Error('Empty URL')
      const res = await chromeRequest.get(item.url, {
        proxy: {
          protocol: 'http',
          host: '127.0.0.1',
          port: mixedPort
        },
        responseType: 'text'
      })
      const data = res.data as string
      await setOverride(id, newItem.ext, data)
      break
    }
    case 'local': {
      const data = item.file || ''
      await setOverride(id, newItem.ext, data)
      break
    }
  }

  return newItem
}

export async function getOverride(id: string, ext: 'js' | 'yaml' | 'log'): Promise<string> {
  if (!existsSync(overridePath(id, ext))) {
    return ''
  }
  return await readFile(overridePath(id, ext), 'utf-8')
}

export async function setOverride(id: string, ext: 'js' | 'yaml', content: string): Promise<void> {
  await writeFile(overridePath(id, ext), content, 'utf-8')
}


================================================
FILE: src/main/config/profile.ts
================================================
import { readFile, rm, writeFile } from 'fs/promises'
import { existsSync } from 'fs'
import { join } from 'path'
import { app } from 'electron'
import i18next from 'i18next'
import * as chromeRequest from '../utils/chromeRequest'
import { parse, stringify } from '../utils/yaml'
import { defaultProfile } from '../utils/template'
import { subStorePort } from '../resolve/server'
import { mihomoUpgradeConfig } from '../core/mihomoApi'
import { restartCore } from '../core/manager'
import { addProfileUpdater, removeProfileUpdater } from '../core/profileUpdater'
import { mihomoProfileWorkDir, mihomoWorkDir, profileConfigPath, profilePath } from '../utils/dirs'
import { createLogger } from '../utils/logger'
import { getAppConfig } from './app'
import { getControledMihomoConfig } from './controledMihomo'

const profileLogger = createLogger('Profile')

let profileConfig: IProfileConfig
let profileConfigWriteQueue: Promise<void> = Promise.resolve()
let changeProfileQueue: Promise<void> = Promise.resolve()

export async function getProfileConfig(force = false): Promise<IProfileConfig> {
  if (force || !profileConfig) {
    const data = await readFile(profileConfigPath(), 'utf-8')
    profileConfig = parse(data) || { items: [] }
  }
  if (typeof profileConfig !== 'object') profileConfig = { items: [] }
  if (!Array.isArray(profileConfig.items)) profileConfig.items = []
  return structuredClone(profileConfig)
}

export async function setProfileConfig(config: IProfileConfig): Promise<void> {
  profileConfigWriteQueue = profileConfigWriteQueue.then(async () => {
    profileConfig = config
    await writeFile(profileConfigPath(), stringify(config), 'utf-8')
  })
  await profileConfigWriteQueue
}

export async function updateProfileConfig(
  updater: (config: IProfileConfig) => IProfileConfig | Promise<IProfileConfig>
): Promise<IProfileConfig> {
  let result: IProfileConfig | undefined
  profileConfigWriteQueue = profileConfigWriteQueue.then(async () => {
    const data = await readFile(profileConfigPath(), 'utf-8')
    profileConfig = parse(data) || { items: [] }
    if (typeof profileConfig !== 'object') profileConfig = { items: [] }
    if (!Array.isArray(profileConfig.items)) profileConfig.items = []
    profileConfig = await updater(structuredClone(profileConfig))
    result = profileConfig
    await writeFile(profileConfigPath(), stringify(profileConfig), 'utf-8')
  })
  await profileConfigWriteQueue
  return structuredClone(result ?? profileConfig)
}

export async function getProfileItem(id: string | undefined): Promise<IProfileItem | undefined> {
  const { items } = await getProfileConfig()
  if (!id || id === 'default')
    return { id: 'default', type: 'local', name: i18next.t('profiles.emptyProfile') }
  return items.find((item) => item.id === id)
}

export async function changeCurrentProfile(id: string): Promise<void> {
  // 使用队列确保 profile 切换串行执行,避免竞态条件
  let taskError: unknown = null
  changeProfileQueue = changeProfileQueue
    .catch(() => {})
    .then(async () => {
      const { current } = await getProfileConfig()
      if (current === id) return

      try {
        await updateProfileConfig((config) => {
          config.current = id
          return config
        })
        await restartCore()
      } catch (e) {
        // 回滚配置
        await updateProfileConfig((config) => {
          config.current = current
          return config
        })
        taskError = e
      }
    })
  await changeProfileQueue
  if (taskError) {
    throw taskError
  }
}

export async function updateProfileItem(item: IProfileItem): Promise<void> {
  await updateProfileConfig((config) => {
    const index = config.items.findIndex((i) => i.id === item.id)
    if (index === -1) {
      throw new Error('Profile not found')
    }
    config.items[index] = item
    return config
  })
}

export async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
  const newItem = await createProfile(item)
  let shouldChangeCurrent = false
  let newProfileIsCurrentAfterUpdate = false
  await updateProfileConfig((config) => {
    const existingIndex = config.items.findIndex((i) => i.id === newItem.id)
    if (existingIndex !== -1) {
      config.items[existingIndex] = newItem
    } else {
      config.items.push(newItem)
    }
    if (!config.current) {
      shouldChangeCurrent = true
      newProfileIsCurrentAfterUpdate = true
    }
    return config
  })

  // If the new profile will become the current profile, ensure generateProfile is called
  // to prepare working directory before restarting core
  if (newProfileIsCurrentAfterUpdate) {
    const { diffWorkDir } = await getAppConfig()
    if (diffWorkDir) {
      try {
        const { generateProfile } = await import('../core/factory')
        await generateProfile()
      } catch (error) {
        profileLogger.warn('Failed to generate profile for new subscription', error)
      }
    }
  }

  if (shouldChangeCurrent) {
    await changeCurrentProfile(newItem.id)
  }
  await addProfileUpdater(newItem)
}

export async function removeProfileItem(id: string): Promise<void> {
  await removeProfileUpdater(id)

  let shouldRestart = false
  await updateProfileConfig((config) => {
    config.items = config.items?.filter((item) => item.id !== id)
    if (config.current === id) {
      shouldRestart = true
      config.current = config.items.length > 0 ? config.items[0].id : undefined
    }
    return config
  })

  if (existsSync(profilePath(id))) {
    await rm(profilePath(id))
  }
  if (shouldRestart) {
    await restartCore()
  }
  if (existsSync(mihomoProfileWorkDir(id))) {
    await rm(mihomoProfileWorkDir(id), { recursive: true })
  }
}

export async function getCurrentProfileItem(): Promise<IProfileItem> {
  const { current } = await getProfileConfig()
  return (
    (await getProfileItem(current)) || {
      id: 'default',
      type: 'local',
      name: i18next.t('profiles.emptyProfile')
    }
  )
}

interface FetchOptions {
  url: string
  useProxy: boolean
  mixedPort: number
  userAgent: string
  authToken?: string
  timeout: number
  substore: boolean
}

interface FetchResult {
  data: string
  headers: Record<string, string>
}

async function fetchAndValidateSubscription(options: FetchOptions): Promise<FetchResult> {
  const { url, useProxy, mixedPort, userAgent, authToken, timeout, substore } = options

  const headers: Record<string, string> = {
    'User-Agent': userAgent,
    'Accept-Encoding': 'identity'
  }
  if (authToken) headers['Authorization'] = authToken

  let res: chromeRequest.Response<string>
  if (substore) {
    const urlObj = new URL(`http://127.0.0.1:${subStorePort}${url}`)
    urlObj.searchParams.set('target', 'ClashMeta')
    urlObj.searchParams.set('noCache', 'true')
    if (useProxy) {
      urlObj.searchParams.set('proxy', `http://127.0.0.1:${mixedPort}`)
    }
    res = await chromeRequest.get(urlObj.toString(), { headers, responseType: 'text', timeout })
  } else {
    res = await chromeRequest.get(url, {
      headers,
      responseType: 'text',
      timeout,
      proxy: useProxy ? { protocol: 'http', host: '127.0.0.1', port: mixedPort } : false
    })
  }

  if (res.status < 200 || res.status >= 300) {
    throw new Error(`Subscription failed: Request status code ${res.status}`)
  }

  const parsed = parse(res.data) as Record<string, unknown> | null
  if (typeof parsed !== 'object' || parsed === null) {
    throw new Error('Subscription failed: Profile is not a valid YAML')
  }
  if (!parsed['proxies'] && !parsed['proxy-providers']) {
    throw new Error('Subscription failed: Profile missing proxies or providers')
  }

  return { data: res.data, headers: res.headers }
}

export async function createProfile(item: Partial<IProfileItem>): Promise<IProfileItem> {
  const id = item.id || new Date().getTime().toString(16)
  const newItem: IProfileItem = {
    id,
    name: item.name || (item.type === 'remote' ? 'Remote File' : 'Local File'),
    type: item.type || 'local',
    url: item.url,
    substore: item.substore || false,
    interval: item.interval || 0,
    override: item.override || [],
    useProxy: item.useProxy || false,
    allowFixedInterval: item.allowFixedInterval || false,
    autoUpdate: item.autoUpdate ?? false,
    authToken: item.authToken,
    updated: new Date().getTime(),
    updateTimeout: item.updateTimeout || 5
  }

  // Local
  if (newItem.type === 'local') {
    await setProfileStr(id, item.file || '')
    return newItem
  }

  // Remote
  if (!item.url) throw new Error('Empty URL')

  const { userAgent, subscriptionTimeout = 30000 } = await getAppConfig()
  const { 'mixed-port': mixedPort = 7890 } = await getControledMihomoConfig()
  const userItemTimeoutMs = (newItem.updateTimeout || 5) * 1000

  const baseOptions: Omit<FetchOptions, 'useProxy' | 'timeout'> = {
    url: item.url,
    mixedPort,
    userAgent: userAgent || `mihomo.party/v${app.getVersion()} (clash.meta)`,
    authToken: item.authToken,
    substore: newItem.substore || false
  }

  const fetchSub = (useProxy: boolean, timeout: number) =>
    fetchAndValidateSubscription({ ...baseOptions, useProxy, timeout })

  let result: FetchResult
  if (newItem.useProxy || newItem.substore) {
    result = await fetchSub(Boolean(newItem.useProxy), userItemTimeoutMs)
  } else {
    try {
      result = await fetchSub(false, userItemTimeoutMs)
    } catch (directError) {
      try {
        // smart fallback
        result = await fetchSub(true, subscriptionTimeout)
      } catch {
        throw directError
      }
    }
  }

  const { data, headers } = result

  if (headers['content-disposition'] && newItem.name === 'Remote File') {
    newItem.name = parseFilename(headers['content-disposition'])
  }
  if (headers['profile-web-page-url']) {
    newItem.home = headers['profile-web-page-url']
  }
  if (headers['profile-update-interval'] && !item.allowFixedInterval) {
    newItem.interval = parseInt(headers['profile-update-interval']) * 60
  }
  if (headers['subscription-userinfo']) {
    newItem.extra = parseSubinfo(headers['subscription-userinfo'])
  }

  await setProfileStr(id, data)
  return newItem
}

export async function getProfileStr(id: string | undefined): Promise<string> {
  if (existsSync(profilePath(id || 'default'))) {
    return await readFile(profilePath(id || 'default'), 'utf-8')
  } else {
    return stringify(defaultProfile)
  }
}

export async function setProfileStr(id: string, content: string): Promise<void> {
  // 读取最新的配置
  const { current } = await getProfileConfig(true)
  await writeFile(profilePath(id), content, 'utf-8')
  if (current === id) {
    try {
      const { generateProfile } = await import('../core/factory')
      await generateProfile()
      await mihomoUpgradeConfig()
      profileLogger.info('Config reloaded successfully using mihomoUpgradeConfig')
    } catch (error) {
      profileLogger.error('Failed to reload config with mihomoUpgradeConfig', error)
      try {
        profileLogger.info('Falling back to restart core')
        const { restartCore } = await import('../core/manager')
        await restartCore()
        profileLogger.info('Core restarted successfully')
      } catch (restartError) {
        profileLogger.error('Failed to restart core', restartError)
        throw restartError
      }
    }
  }
}

export async function getProfile(id: string | undefined): Promise<IMihomoConfig> {
  const profile = await getProfileStr(id)

  // 检测是否为 HTML 内容(订阅返回错误页面)
  const trimmed = profile.trim()
  if (
    trimmed.startsWith('<!DOCTYPE') ||
    trimmed.startsWith('<html') ||
    trimmed.startsWith('<HTML') ||
    /<style[^>]*>/i.test(trimmed.slice(0, 500))
  ) {
    throw new Error(
      `Profile "${id}" contains HTML instead of YAML. The subscription may have returned an error page. Please re-import or update the subscription.`
    )
  }

  try {
    let result = parse(profile)
    if (typeof result !== 'object') result = {}
    return result as IMihomoConfig
  } catch (e) {
    const msg = e instanceof Error ? e.message : String(e)
    throw new Error(`Failed to parse profile "${id}": ${msg}`)
  }
}

// attachment;filename=xxx.yaml; filename*=UTF-8''%xx%xx%xx
function parseFilename(str: string): string {
  if (str.match(/filename\*=.*''/)) {
    const parts = str.split(/filename\*=.*''/)
    if (parts[1]) {
      return decodeURIComponent(parts[1])
    }
  }
  const parts = str.split('filename=')
  if (parts[1]) {
    return parts[1].replace(/^["']|["']$/g, '')
  }
  return 'Remote File'
}

// subscription-userinfo: upload=1234; download=2234; total=1024000; expire=2218532293
function parseSubinfo(str: string): ISubscriptionUserInfo {
  const parts = str.split(/\s*;\s*/)
  const obj = {} as ISubscriptionUserInfo
  parts.forEach((part) => {
    const [key, value] = part.split('=')
    obj[key] = parseInt(value)
  })
  return obj
}

function isAbsolutePath(path: string): boolean {
  return path.startsWith('/') || /^[a-zA-Z]:\\/.test(path)
}

export async function getFileStr(path: string): Promise<string> {
  const { diffWorkDir = false } = await getAppConfig()
  const { current } = await getProfileConfig()
  if (isAbsolutePath(path)) {
    return await readFile(path, 'utf-8')
  } else {
    return await readFile(
      join(diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), path),
      'utf-8'
    )
  }
}

export async function setFileStr(path: string, content: string): Promise<void> {
  const { diffWorkDir = false } = await getAppConfig()
  const { current } = await getProfileConfig()
  if (isAbsolutePath(path)) {
    await writeFile(path, content, 'utf-8')
  } else {
    await writeFile(
      join(diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), path),
      content,
      'utf-8'
    )
  }
}

export async function convertMrsRuleset(filePath: string, behavior: string): Promise<string> {
  const { exec } = await import('child_process')
  const { promisify } = await import('util')
  const execAsync = promisify(exec)
  const { mihomoCorePath } = await import('../utils/dirs')
  const { getAppConfig } = await import('./app')
  const { tmpdir } = await import('os')
  const { randomBytes } = await import('crypto')
  const { unlink } = await import('fs/promises')

  const { core = 'mihomo' } = await getAppConfig()
  const corePath = mihomoCorePath(core)
  const { diffWorkDir = false } = await getAppConfig()
  const { current } = await getProfileConfig()
  let fullPath: string
  if (isAbsolutePath(filePath)) {
    fullPath = filePath
  } else {
    fullPath = join(diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(), filePath)
  }

  const tempFileName = `mrs-convert-${randomBytes(8).toString('hex')}.txt`
  const tempFilePath = join(tmpdir(), tempFileName)

  try {
    // 使用 mihomo convert-ruleset 命令转换 MRS 文件为 text 格式
    // 命令格式: mihomo convert-ruleset <behavior> <format> <source>
    await execAsync(`"${corePath}" convert-ruleset ${behavior} mrs "${fullPath}" "${tempFilePath}"`)
    const content = await readFile(tempFilePath, 'utf-8')
    await unlink(tempFilePath)

    return content
  } catch (error) {
    try {
      await unlink(tempFilePath)
    } catch {
      // ignore
    }
    throw error
  }
}


================================================
FILE: src/main/config/smartOverride.ts
================================================
import { overrideLogger } from '../utils/logger'
import { getAppConfig } from './app'
import { addOverrideItem, removeOverrideItem, getOverrideItem } from './override'

const SMART_OVERRIDE_ID = 'smart-core-override'

/**
 * Smart 内核的覆写配置模板
 */
function generateSmartOverrideTemplate(
  useLightGBM: boolean,
  collectData: boolean,
  strategy: string,
  collectorSize: number
): string {
  return `
// 配置会在启用 Smart 内核时自动应用

function main(config) {
  try {
    // 确保配置对象存在
    if (!config || typeof config !== 'object') {
      console.log('[Smart Override] Invalid config object')
      return config
    }

    // 设置 Smart 内核的 profile 配置
    if (!config.profile) {
      config.profile = {}
    }
    config.profile['smart-collector-size'] = ${collectorSize}

    // 确保代理组配置存在
    if (!config['proxy-groups']) {
      config['proxy-groups'] = []
    }

    // 确保代理组是数组
    if (!Array.isArray(config['proxy-groups'])) {
      console.log('[Smart Override] proxy-groups is not an array, converting...')
      config['proxy-groups'] = []
    }

    // 首先检查是否存在 url-test 或 load-balance 代理组
    let hasUrlTestOrLoadBalance = false
    for (let i = 0; i < config['proxy-groups'].length; i++) {
      const group = config['proxy-groups'][i]
      if (group && group.type) {
        const groupType = group.type.toLowerCase()
        if (groupType === 'url-test' || groupType === 'load-balance') {
          hasUrlTestOrLoadBalance = true
          break
        }
      }
    }

    // 如果存在 url-test 或 load-balance 代理组,只进行类型转换
    if (hasUrlTestOrLoadBalance) {
      console.log('[Smart Override] Found url-test or load-balance groups, converting to smart type')
      
      // 记录需要更新引用的代理组名称映射
      const nameMapping = new Map()
      
      for (let i = 0; i < config['proxy-groups'].length; i++) {
        const group = config['proxy-groups'][i]
        if (group && group.type) {
          const groupType = group.type.toLowerCase()
          if (groupType === 'url-test' || groupType === 'load-balance') {
            console.log('[Smart Override] Converting group:', group.name, 'from', group.type, 'to smart')
            
            // 记录原名称和新名称的映射关系
            const originalName = group.name
            
            // 保留原有配置,只修改 type 和添加 Smart 特有配置
            group.type = 'smart'
            
            // 为代理组名称添加 (Smart Group) 后缀
            if (group.name && !group.name.includes('(Smart Group)')) {
              group.name = group.name + '(Smart Group)'
              nameMapping.set(originalName, group.name)
            }
            
            // 添加 Smart 特有配置
            if (!group['policy-priority']) {
              group['policy-priority'] = ''  // policy-priority: <1 means lower priority, >1 means higher priority, the default is 1, pattern support regex and string
            }
            group.uselightgbm = ${useLightGBM}
            group.collectdata = ${collectData}
            group.strategy = '${strategy}'
            
            // 移除 url-test 和 load-balance 特有的配置
            if (group.url) delete group.url
            if (group.interval) delete group.interval
            if (group.tolerance) delete group.tolerance
            if (group.lazy) delete group.lazy
            if (group.expected_status) delete group['expected-status']
          }
        }
      }
      
      // 更新配置文件中其他位置对代理组名称的引用
      if (nameMapping.size > 0) {
        console.log('[Smart Override] Updating references to renamed groups:', Array.from(nameMapping.entries()))
        
        // 更新代理组中的 proxies 字段引用
        if (config['proxy-groups'] && Array.isArray(config['proxy-groups'])) {
          config['proxy-groups'].forEach(group => {
            if (group && group.proxies && Array.isArray(group.proxies)) {
              group.proxies = group.proxies.map(proxyName => {
                if (nameMapping.has(proxyName)) {
                  console.log('[Smart Override] Updated proxy reference:', proxyName, '→', nameMapping.get(proxyName))
                  return nameMapping.get(proxyName)
                }
                return proxyName
              })
            }
          })
        }
        
        // 更新规则中的代理组引用
        // 规则参数列表,这些不是策略组名称
        const ruleParamsSet = new Set(['no-resolve', 'force-remote-dns', 'prefer-ipv6'])
        
        if (config.rules && Array.isArray(config.rules)) {
          config.rules = config.rules.map(rule => {
            if (typeof rule === 'string') {
              // 按逗号分割规则,精确匹配策略组名称位置
              const parts = rule.split(',').map(part => part.trim())
              
              if (parts.length >= 2) {
                // 找到策略组名称的位置
                let targetIndex = -1
                
                // MATCH 规则:MATCH,策略组
                if (parts[0] === 'MATCH' && parts.length === 2) {
                  targetIndex = 1
                } else if (parts.length >= 3) {
                  // 其他规则:TYPE,MATCHER,策略组[,参数...]
                  // 策略组通常在第 3 个位置(索引 2),但需要跳过参数
                  for (let i = 2; i < parts.length; i++) {
                    if (!ruleParamsSet.has(parts[i])) {
                      targetIndex = i
                      break
                    }
                  }
                }
                
                // 只替换策略组名称位置
                if (targetIndex !== -1 && nameMapping.has(parts[targetIndex])) {
                  const oldName = parts[targetIndex]
                  parts[targetIndex] = nameMapping.get(oldName)
                  console.log('[Smart Override] Updated rule reference:', oldName, '→', nameMapping.get(oldName))
                  return parts.join(',')
                }
              }
              return rule
            } else if (typeof rule === 'object' && rule !== null) {
              // 处理对象格式的规则
              ['target', 'proxy'].forEach(field => {
                if (rule[field] && nameMapping.has(rule[field])) {
                  console.log('[Smart Override] Updated rule object reference:', rule[field], '→', nameMapping.get(rule[field]))
                  rule[field] = nameMapping.get(rule[field])
                }
              })
            }
            return rule
          })
        }
        
        // 更新其他可能的配置字段引用
        ['mode', 'proxy-mode'].forEach(field => {
          if (config[field] && nameMapping.has(config[field])) {
            console.log('[Smart Override] Updated config field', field + ':', config[field], '→', nameMapping.get(config[field]))
            config[field] = nameMapping.get(config[field])
          }
        })
      }
      
      console.log('[Smart Override] Conversion completed, skipping other operations')
      return config
    }

    // 如果没有 url-test 或 load-balance 代理组,执行原有逻辑
    console.log('[Smart Override] No url-test or load-balance groups found, executing original logic')
    
    // 查找现有的 Smart 代理组并更新
    let smartGroupExists = false
    for (let i = 0; i < config['proxy-groups'].length; i++) {
      const group = config['proxy-groups'][i]
      if (group && group.type === 'smart') {
        smartGroupExists = true
        console.log('[Smart Override] Found existing smart group:', group.name)

        if (!group['policy-priority']) {
          group['policy-priority'] = ''  // policy-priority: <1 means lower priority, >1 means higher priority, the default is 1, pattern support regex and string
        }
        group.uselightgbm = ${useLightGBM}
        group.collectdata = ${collectData}
        group.strategy = '${strategy}'
        break
      }
    }

    // 如果没有 Smart 组且有可用代理,创建示例组
    if (!smartGroupExists && config.proxies && Array.isArray(config.proxies) && config.proxies.length > 0) {
      console.log('[Smart Override] Creating new smart group with', config.proxies.length, 'proxies')

      // 获取所有代理的名称
      const proxyNames = config.proxies
        .filter(proxy => proxy && typeof proxy === 'object' && proxy.name)
        .map(proxy => proxy.name)

      if (proxyNames.length > 0) {
        const smartGroup = {
          name: 'Smart Group',
          type: 'smart',
          'policy-priority': '',  // policy-priority: <1 means lower priority, >1 means higher priority, the default is 1, pattern support regex and string
          uselightgbm: ${useLightGBM},
          collectdata: ${collectData},
          strategy: '${strategy}',
          proxies: proxyNames
        }
        config['proxy-groups'].unshift(smartGroup)
        console.log('[Smart Override] Created smart group at first position with proxies:', proxyNames)
      } else {
        console.log('[Smart Override] No valid proxies found, skipping smart group creation')
      }
    } else if (!smartGroupExists) {
      console.log('[Smart Override] No proxies available, skipping smart group creation')
    }

    // 处理规则替换
    if (config.rules && Array.isArray(config.rules)) {
      console.log('[Smart Override] Processing rules, original count:', config.rules.length)

      // 收集所有代理组名称
      const proxyGroupNames = new Set()
      if (config['proxy-groups'] && Array.isArray(config['proxy-groups'])) {
        config['proxy-groups'].forEach(group => {
          if (group && group.name) {
            proxyGroupNames.add(group.name)
          }
        })
      }

      // 添加常见的内置目标
      const builtinTargets = new Set([
        'DIRECT',
        'REJECT',
        'REJECT-DROP',
        'PASS',
        'COMPATIBLE'
      ])

      // 添加常见的规则参数,不应该替换
      const ruleParams = new Set(['no-resolve', 'force-remote-dns', 'prefer-ipv6'])

      console.log('[Smart Override] Found', proxyGroupNames.size, 'proxy groups:', Array.from(proxyGroupNames))

      let replacedCount = 0
      config.rules = config.rules.map(rule => {
        if (typeof rule === 'string') {
          // 检查是否是复杂规则格式(包含括号的嵌套规则)
          if (rule.includes('((') || rule.includes('))')) {
            console.log('[Smart Override] Skipping complex nested rule:', rule)
            return rule
          }

          // 处理字符串格式的规则
          const parts = rule.split(',').map(part => part.trim())
          if (parts.length >= 2) {
            // 找到代理组名称的位置
            let targetIndex = -1
            let targetValue = ''

            // 处理 MATCH 规则
            if (parts[0] === 'MATCH' && parts.length === 2) {
              targetIndex = 1
              targetValue = parts[1]
            } else if (parts.length >= 3) {
              // 处理其他规则
              for (let i = 2; i < parts.length; i++) {
                const part = parts[i]
                if (!ruleParams.has(part)) {
                  targetIndex = i
                  targetValue = part
                  break
                }
              }
            }

            if (targetIndex !== -1 && targetValue) {
              // 检查是否应该替换
              const shouldReplace = !builtinTargets.has(targetValue) &&
                                   (proxyGroupNames.has(targetValue) ||
                                    !ruleParams.has(targetValue))

              if (shouldReplace) {
                parts[targetIndex] = 'Smart Group'
                replacedCount++
                console.log('[Smart Override] Replaced rule target:', targetValue, '→ Smart Group')
                return parts.join(',')
              }
            }
          }
        } else if (typeof rule === 'object' && rule !== null) {
          // 处理对象格式
          let targetField = ''
          let targetValue = ''

          if (rule.target) {
            targetField = 'target'
            targetValue = rule.target
          } else if (rule.proxy) {
            targetField = 'proxy'
            targetValue = rule.proxy
          }

          if (targetField && targetValue) {
            const shouldReplace = !builtinTargets.has(targetValue) &&
                                 (proxyGroupNames.has(targetValue) ||
                                  !ruleParams.has(targetValue))

            if (shouldReplace) {
              rule[targetField] = 'Smart Group'
              replacedCount++
              console.log('[Smart Override] Replaced rule target:', targetValue, '→ Smart Group')
            }
          }
        }
        return rule
      })

      console.log('[Smart Override] Rules processed, replaced', replacedCount, 'non-DIRECT rules with Smart Group')
    } else {
      console.log('[Smart Override] No rules found or rules is not an array')
    }

    console.log('[Smart Override] Configuration processed successfully')
    return config
  } catch (error) {
    console.error('[Smart Override] Error processing config:', error)
    // 发生错误时返回原始配置,避免破坏整个配置
    return config
  }
}
`
}

/**
 * 创建或更新 Smart 内核覆写配置
 */
export async function createSmartOverride(): Promise<void> {
  try {
    // 获取应用配置
    const {
      smartCoreUseLightGBM = false,
      smartCoreCollectData = false,
      smartCoreStrategy = 'sticky-sessions',
      smartCollectorSize = 100
    } = await getAppConfig()

    // 生成覆写模板
    const template = generateSmartOverrideTemplate(
      smartCoreUseLightGBM,
      smartCoreCollectData,
      smartCoreStrategy,
      smartCollectorSize
    )

    await addOverrideItem({
      id: SMART_OVERRIDE_ID,
      name: 'Smart Core Override',
      type: 'local',
      ext: 'js',
      global: true,
      file: template
    })
  } catch (error) {
    await overrideLogger.error('Failed to create Smart override', error)
    throw error
  }
}

/**
 * 删除 Smart 内核覆写配置
 */
export async function removeSmartOverride(): Promise<void> {
  try {
    const existingOverride = await getOverrideItem(SMART_OVERRIDE_ID)
    if (existingOverride) {
      await removeOverrideItem(SMART_OVERRIDE_ID)
    }
  } catch (error) {
    await overrideLogger.error('Failed to remove Smart override', error)
    throw error
  }
}

/**
 * 根据应用配置管理 Smart 覆写
 */
export async function manageSmartOverride(): Promise<void> {
  const { enableSmartCore = true, enableSmartOverride = true, core } = await getAppConfig()

  if (enableSmartCore && enableSmartOverride && core === 'mihomo-smart') {
    await createSmartOverride()
  } else {
    await removeSmartOverride()
  }
}

/**
 * 检查 Smart 覆写是否存在
 */
export async function isSmartOverrideExists(): Promise<boolean> {
  try {
    const override = await getOverrideItem(SMART_OVERRIDE_ID)
    return !!override
  } catch {
    return false
  }
}


================================================
FILE: src/main/core/dns.ts
================================================
import { exec } from 'child_process'
import { promisify } from 'util'
import { net } from 'electron'
import axios from 'axios'
import { getAppConfig, patchAppConfig } from '../config'

const execPromise = promisify(exec)
const helperSocketPath = '/tmp/mihomo-party-helper.sock'

let setPublicDNSTimer: NodeJS.Timeout | null = null
let recoverDNSTimer: NodeJS.Timeout | null = null

export async function getDefaultDevice(): Promise<string> {
  const { stdout: deviceOut } = await execPromise(`route -n get default`)
  let device = deviceOut.split('\n').find((s) => s.includes('interface:'))
  device = device?.trim().split(' ').slice(1).join(' ')
  if (!device) throw new Error('Get device failed')
  return device
}

async function getDefaultService(): Promise<string> {
  const device = await getDefaultDevice()
  const { stdout: order } = await execPromise(`networksetup -listnetworkserviceorder`)
  const block = order.split('\n\n').find((s) => s.includes(`Device: ${device}`))
  if (!block) throw new Error('Get networkservice failed')
  for (const line of block.split('\n')) {
    if (line.match(/^\(\d+\).*/)) {
      return line.trim().split(' ').slice(1).join(' ')
    }
  }
  throw new Error('Get service failed')
}

async function getOriginDNS(): Promise<void> {
  const service = await getDefaultService()
  const { stdout: dns } = await execPromise(`networksetup -getdnsservers "${service}"`)
  if (dns.startsWith("There aren't any DNS Servers set on")) {
    await patchAppConfig({ originDNS: 'Empty' })
  } else {
    await patchAppConfig({ originDNS: dns.trim().replace(/\n/g, ' ') })
  }
}

async function setDNS(dns: string): Promise<void> {
  const service = await getDefaultService()
  try {
    await axios.post('http://localhost/dns', { service, dns }, { socketPath: helperSocketPath })
  } catch {
    // fallback to osascript if helper not available
    const shell = `networksetup -setdnsservers "${service}" ${dns}`
    const command = `do shell script "${shell}" with administrator privileges`
    await execPromise(`osascript -e '${command}'`)
  }
}

export async function setPublicDNS(): Promise<void> {
  if (process.platform !== 'darwin') return
  if (net.isOnline()) {
    const { originDNS } = await getAppConfig()
    if (!originDNS) {
      await getOriginDNS()
      await setDNS('223.5.5.5')
    }
  } else {
    if (setPublicDNSTimer) clearTimeout(setPublicDNSTimer)
    setPublicDNSTimer = setTimeout(() => setPublicDNS(), 5000)
  }
}

export async function recoverDNS(): Promise<void> {
  if (process.platform !== 'darwin') return
  if (net.isOnline()) {
    const { originDNS } = await getAppConfig()
    if (originDNS) {
      await setDNS(originDNS)
      await patchAppConfig({ originDNS: undefined })
    }
  } else {
    if (recoverDNSTimer) clearTimeout(recoverDNSTimer)
    recoverDNSTimer = setTimeout(() => recoverDNS(), 5000)
  }
}


================================================
FILE: src/main/core/factory.ts
================================================
import { copyFile, mkdir, writeFile, readFile, stat } from 'fs/promises'
import vm from 'vm'
import { existsSync, writeFileSync } from 'fs'
import path from 'path'
import {
  getControledMihomoConfig,
  getProfileConfig,
  getProfile,
  getProfileItem,
  getOverride,
  getOverrideItem,
  getOverrideConfig,
  getAppConfig
} from '../config'
import {
  mihomoProfileWorkDir,
  mihomoWorkConfigPath,
  mihomoWorkDir,
  overridePath,
  rulePath
} from '../utils/dirs'
import { parse, stringify } from '../utils/yaml'
import { deepMerge } from '../utils/merge'
import { createLogger } from '../utils/logger'

const factoryLogger = createLogger('Factory')

let runtimeConfigStr: string = ''
let runtimeConfig: IMihomoConfig = {} as IMihomoConfig

// 辅助函数:处理带偏移量的规则
function processRulesWithOffset(ruleStrings: string[], currentRules: string[], isAppend = false) {
  const normalRules: string[] = []
  const rules = [...currentRules]

  ruleStrings.forEach((ruleStr) => {
    const parts = ruleStr.split(',')
    const firstPartIsNumber =
      !isNaN(Number(parts[0])) && parts[0].trim() !== '' && parts.length >= 3

    if (firstPartIsNumber) {
      const offset = parseInt(parts[0])
      const rule = parts.slice(1).join(',')

      if (isAppend) {
        // 后置规则的插入位置计算
        const insertPosition = Math.max(0, rules.length - Math.min(offset, rules.length))
        rules.splice(insertPosition, 0, rule)
      } else {
        // 前置规则的插入位置计算
        const insertPosition = Math.min(offset, rules.length)
        rules.splice(insertPosition, 0, rule)
      }
    } else {
      normalRules.push(ruleStr)
    }
  })

  return { normalRules, insertRules: rules }
}

export async function generateProfile(): Promise<string | undefined> {
  // 读取最新的配置
  const { current } = await getProfileConfig(true)
  const {
    diffWorkDir = false,
    controlDns = true,
    controlSniff = true,
    useNameserverPolicy
  } = await getAppConfig()
  const currentProfile = await overrideProfile(current, await getProfile(current))
  let controledMihomoConfig = await getControledMihomoConfig()

  // 根据开关状态过滤控制配置
  controledMihomoConfig = { ...controledMihomoConfig }
  if (!controlDns) {
    delete controledMihomoConfig.dns
    delete controledMihomoConfig.hosts
  }
  if (!controlSniff) {
    delete controledMihomoConfig.sniffer
  }
  if (!useNameserverPolicy) {
    delete controledMihomoConfig?.dns?.['nameserver-policy']
  }

  // 应用规则文件
  try {
    const ruleFilePath = rulePath(current || 'default')
    if (existsSync(ruleFilePath)) {
      const ruleFileContent = await readFile(ruleFilePath, 'utf-8')
      const ruleData = parse(ruleFileContent) as {
        prepend?: string[]
        append?: string[]
        delete?: string[]
      } | null

      if (ruleData && typeof ruleData === 'object') {
        // 确保 rules 数组存在
        if (!currentProfile.rules) {
          currentProfile.rules = [] as unknown as []
        }

        let rules = [...currentProfile.rules] as unknown as string[]

        // 处理前置规则
        if (ruleData.prepend?.length) {
          const { normalRules: prependRules, insertRules } = processRulesWithOffset(
            ruleData.prepend,
            rules
          )
          rules = [...prependRules, ...insertRules]
        }

        // 处理后置规则
        if (ruleData.append?.length) {
          const { normalRules: appendRules, insertRules } = processRulesWithOffset(
            ruleData.append,
            rules,
            true
          )
          rules = [...insertRules, ...appendRules]
        }

        // 处理删除规则
        if (ruleData.delete?.length) {
          const deleteSet = new Set(ruleData.delete)
          rules = rules.filter((rule) => {
            const ruleStr = Array.isArray(rule) ? rule.join(',') : rule
            return !deleteSet.has(ruleStr)
          })
        }

        currentProfile.rules = rules as unknown as []
      }
    }
  } catch (error) {
    factoryLogger.error('Failed to read or apply rule file', error)
  }

  const profile = deepMerge(currentProfile, controledMihomoConfig)
  // 确保可以拿到基础日志信息
  // 使用 debug 可以调试内核相关问题 `debug/pprof`
  if (['info', 'debug'].includes(profile['log-level']) === false) {
    profile['log-level'] = 'info'
  }
  runtimeConfig = profile
  runtimeConfigStr = stringify(profile)
  if (diffWorkDir) {
    await prepareProfileWorkDir(current)
  }
  await writeFile(
    diffWorkDir ? mihomoWorkConfigPath(current) : mihomoWorkConfigPath('work'),
    runtimeConfigStr
  )
  return current
}

async function prepareProfileWorkDir(current: string | undefined): Promise<void> {
  if (!existsSync(mihomoProfileWorkDir(current))) {
    await mkdir(mihomoProfileWorkDir(current), { recursive: true })
  }

  const isSourceNewer = async (sourcePath: string, targetPath: string): Promise<boolean> => {
    try {
      const [sourceStats, targetStats] = await Promise.all([stat(sourcePath), stat(targetPath)])
      return sourceStats.mtime > targetStats.mtime
    } catch {
      return true
    }
  }

  const copy = async (file: string): Promise<void> => {
    const targetPath = path.join(mihomoProfileWorkDir(current), file)
    const sourcePath = path.join(mihomoWorkDir(), file)
    if (!existsSync(sourcePath)) return
    // 复制条件:目标不存在 或 源文件更新
    const shouldCopy = !existsSync(targetPath) || (await isSourceNewer(sourcePath, targetPath))
    if (shouldCopy) {
      await copyFile(sourcePath, targetPath)
    }
  }
  await Promise.all([
    copy('country.mmdb'),
    copy('geoip.metadb'),
    copy('geoip.dat'),
    copy('geosite.dat'),
    copy('ASN.mmdb')
  ])
}

async function overrideProfile(
  current: string | undefined,
  profile: IMihomoConfig
): Promise<IMihomoConfig> {
  const { items = [] } = (await getOverrideConfig()) || {}
  const globalOverride = items.filter((item) => item.global).map((item) => item.id)
  const { override = [] } = (await getProfileItem(current)) || {}
  for (const ov of new Set(globalOverride.concat(override))) {
    const item = await getOverrideItem(ov)
    const content = await getOverride(ov, item?.ext || 'js')
    switch (item?.ext) {
      case 'js':
        profile = runOverrideScript(profile, content, item)
        break
      case 'yaml': {
        let patch = parse(content) || {}
        if (typeof patch !== 'object') patch = {}
        profile = deepMerge(profile, patch)
        break
      }
    }
  }
  return profile
}

function runOverrideScript(
  profile: IMihomoConfig,
  script: string,
  item: IOverrideItem
): IMihomoConfig {
  const log = (type: string, data: string, flag = 'a'): void => {
    writeFileSync(overridePath(item.id, 'log'), `[${type}] ${data}\n`, {
      encoding: 'utf-8',
      flag
    })
  }
  try {
    const ctx = {
      console: Object.freeze({
        log(data: never) {
          log('log', JSON.stringify(data))
        },
        info(data: never) {
          log('info', JSON.stringify(data))
        },
        error(data: never) {
          log('error', JSON.stringify(data))
        },
        debug(data: never) {
          log('debug', JSON.stringify(data))
        }
      })
    }
    vm.createContext(ctx)
    const code = `${script} main(${JSON.stringify(profile)})`
    log('info', '开始执行脚本', 'w')
    const newProfile = vm.runInContext(code, ctx)
    if (typeof newProfile !== 'object') {
      throw new Error('脚本返回值必须是对象')
    }
    log('info', '脚本执行成功')
    return newProfile
  } catch (e) {
    log('exception', `脚本执行失败:${e}`)
    return profile
  }
}

export async function getRuntimeConfigStr(): Promise<string> {
  return runtimeConfigStr
}

export async function getRuntimeConfig(): Promise<IMihomoConfig> {
  return runtimeConfig
}


================================================
FILE: src/main/core/manager.ts
================================================
import { ChildProcess, execFile, spawn } from 'child_process'
import { readFile, rm, writeFile } from 'fs/promises'
import { promisify } from 'util'
import path from 'path'
import os from 'os'
import { createWriteStream, existsSync } from 'fs'
import chokidar, { FSWatcher } from 'chokidar'
import { app, ipcMain } from 'electron'
import { mainWindow } from '../window'
import {
  getAppConfig,
  getControledMihomoConfig,
  patchControledMihomoConfig,
  manageSmartOverride
} from '../config'
import {
  dataDir,
  coreLogPath,
  mihomoCoreDir,
  mihomoCorePath,
  mihomoProfileWorkDir,
  mihomoTestDir,
  mihomoWorkConfigPath,
  mihomoWorkDir
} from '../utils/dirs'
import { uploadRuntimeConfig } from '../resolve/gistApi'
import { startMonitor } from '../resolve/trafficMonitor'
import { safeShowErrorBox } from '../utils/init'
import i18next from '../../shared/i18n'
import { managerLogger } from '../utils/logger'
import {
  startMihomoTraffic,
  startMihomoConnections,
  startMihomoLogs,
  startMihomoMemory,
  stopMihomoConnections,
  stopMihomoTraffic,
  stopMihomoLogs,
  stopMihomoMemory,
  patchMihomoConfig,
  getAxios
} from './mihomoApi'
import { generateProfile } from './factory'
import { getSessionAdminStatus } from './permissions'
import {
  cleanupSocketFile,
  cleanupWindowsNamedPipes,
  validateWindowsPipeAccess,
  waitForCoreReady
} from './process'
import { setPublicDNS, recoverDNS } from './dns'

// 重新导出权限相关函数
export {
  initAdminStatus,
  getSessionAdminStatus,
  checkAdminPrivileges,
  checkMihomoCorePermissions,
  checkHighPrivilegeCore,
  grantTunPermissions,
  restartAsAdmin,
  requestTunPermissions,
  showTunPermissionDialog,
  showErrorDialog,
  checkTunPermissions,
  manualGrantCorePermition
} from './permissions'

export { getDefaultDevice } from './dns'

const execFilePromise = promisify(execFile)
const ctlParam = process.platform === 'win32' ? '-ext-ctl-pipe' : '-ext-ctl-unix'

// 核心进程状态
let child: ChildProcess
let retry = 10
let isRestarting = false

// 文件监听器
let coreWatcher: FSWatcher | null = null

// 初始化核心文件监听
export function initCoreWatcher(): void {
  if (coreWatcher) return

  coreWatcher = chokidar.watch(path.join(mihomoCoreDir(), 'meta-update'), {})
  coreWatcher.on('unlinkDir', async () => {
    // 等待核心自我更新完成,避免与核心自动重启产生竞态
    await new Promise((resolve) => setTimeout(resolve, 3000))
    try {
      await stopCore(true)
      await startCore()
    } catch (e) {
      safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
    }
  })
}

// 清理核心文件监听
export function cleanupCoreWatcher(): void {
  if (coreWatcher) {
    coreWatcher.close()
    coreWatcher = null
  }
}

// 动态生成 IPC 路径
export const getMihomoIpcPath = (): string => {
  if (process.platform === 'win32') {
    const isAdmin = getSessionAdminStatus()
    const sessionId = process.env.SESSIONNAME || process.env.USERNAME || 'default'
    const processId = process.pid

    return isAdmin
      ? `\\\\.\\pipe\\MihomoParty\\mihomo-admin-${sessionId}-${processId}`
      : `\\\\.\\pipe\\MihomoParty\\mihomo-user-${sessionId}-${processId}`
  }

  const uid = process.getuid?.() || 'unknown'
  const processId = process.pid
  return `/tmp/mihomo-party-${uid}-${processId}.sock`
}

// 核心配置接口
interface CoreConfig {
  corePath: string
  workDir: string
  ipcPath: string
  logLevel: LogLevel
  tunEnabled: boolean
  autoSetDNS: boolean
  cpuPriority: string
  detached: boolean
}

// 准备核心配置
async function prepareCore(detached: boolean, skipStop = false): Promise<CoreConfig> {
  const [appConfig, mihomoConfig] = await Promise.all([getAppConfig(), getControledMihomoConfig()])

  const {
    core = 'mihomo',
    autoSetDNS = true,
    diffWorkDir = false,
    mihomoCpuPriority = 'PRIORITY_NORMAL'
  } = appConfig

  const { 'log-level': logLevel = 'info' as LogLevel, tun } = mihomoConfig

  // 清理旧进程
  const pidPath = path.join(dataDir(), 'core.pid')
  if (existsSync(pidPath)) {
    const pid = parseInt(await readFile(pidPath, 'utf-8'))
    try {
      process.kill(pid, 'SIGINT')
    } catch {
      // ignore
    } finally {
      await rm(pidPath)
    }
  }

  // 管理 Smart 内核覆写配置
  await manageSmartOverride()

  // generateProfile 返回实际使用的 current
  const current = await generateProfile()
  await checkProfile(current, core, diffWorkDir)
  if (!skipStop) {
    await stopCore()
  }
  await cleanupSocketFile()

  // 设置 DNS
  if (tun?.enable && autoSetDNS) {
    try {
      await setPublicDNS()
    } catch (error) {
      managerLogger.error('set dns failed', error)
    }
  }

  // 获取动态 IPC 路径
  const ipcPath = getMihomoIpcPath()
  managerLogger.info(`Using IPC path: ${ipcPath}`)

  if (process.platform === 'win32') {
    await validateWindowsPipeAccess(ipcPath)
  }

  return {
    corePath: mihomoCorePath(core),
    workDir: diffWorkDir ? mihomoProfileWorkDir(current) : mihomoWorkDir(),
    ipcPath,
    logLevel,
    tunEnabled: tun?.enable ?? false,
    autoSetDNS,
    cpuPriority: mihomoCpuPriority,
    detached
  }
}

// 启动核心进程
function spawnCoreProcess(config: CoreConfig): ChildProcess {
  const { corePath, workDir, ipcPath, cpuPriority, detached } = config

  const stdout = createWriteStream(coreLogPath(), { flags: 'a' })
  const stderr = createWriteStream(coreLogPath(), { flags: 'a' })

  const proc = spawn(corePath, ['-d', workDir, ctlParam, ipcPath], {
    detached,
    stdio: detached ? 'ignore' : undefined
  })

  if (process.platform === 'win32' && proc.pid) {
    os.setPriority(
      proc.pid,
      os.constants.priority[cpuPriority as keyof typeof os.constants.priority]
    )
  }

  if (!detached) {
    proc.stdout?.pipe(stdout)
    proc.stderr?.pipe(stderr)
  }

  return proc
}

// 设置核心进程事件监听
function setupCoreListeners(
  proc: ChildProcess,
  logLevel: LogLevel,
  resolve: (value: Promise<void>[]) => void,
  reject: (reason: unknown) => void
): void {
  proc.on('close', async (code, signal) => {
    managerLogger.info(`Core closed, code: ${code}, signal: ${signal}`)

    if (isRestarting) {
      managerLogger.info('Core closed during restart, skipping auto-restart')
      return
    }

    if (retry) {
      managerLogger.info('Try Restart Core')
      retry--
      await restartCore()
    } else {
      await stopCore()
    }
  })

  proc.stdout?.on('data', async (data) => {
    const str = data.toString()

    // TUN 权限错误
    if (str.includes('configure tun interface: operation not permitted')) {
      patchControledMihomoConfig({ tun: { enable: false } })
      mainWindow?.webContents.send('controledMihomoConfigUpdated')
      ipcMain.emit('updateTrayMenu')
      reject(i18next.t('tun.error.tunPermissionDenied'))
      return
    }

    // 控制器监听错误
    const isControllerError =
      (process.platform !== 'win32' && str.includes('External controller unix listen error')) ||
      (process.platform === 'win32' && str.includes('External controller pipe listen error'))

    if (isControllerError) {
      managerLogger.error('External controller listen error detected:', str)

      if (process.platform === 'win32') {
        managerLogger.info('Attempting Windows pipe cleanup and retry...')
        try {
          await cleanupWindowsNamedPipes()
          await new Promise((r) => setTimeout(r, 2000))
        } catch (cleanupError) {
          managerLogger.error('Pipe cleanup failed:', cleanupError)
        }
      }

      reject(i18next.t('mihomo.error.externalControllerListenError'))
      return
    }

    // API 就绪
    const isApiReady =
      (process.platform !== 'win32' && str.includes('RESTful API unix listening at')) ||
      (process.platform === 'win32' && str.includes('RESTful API pipe listening at'))

    if (isApiReady) {
      resolve([
        new Promise((innerResolve) => {
          proc.stdout?.on('data', async (innerData) => {
            if (
              innerData
                .toString()
                .toLowerCase()
                .includes('start initial compatible provider default')
            ) {
              try {
                mainWindow?.webContents.send('groupsUpdated')
                mainWindow?.webContents.send('rulesUpdated')
                await uploadRuntimeConfig()
              } catch {
                // ignore
              }
              await patchMihomoConfig({ 'log-level': logLevel })
              innerResolve()
            }
          })
        })
      ])

      await waitForCoreReady()
      await getAxios(true)
      await startMihomoTraffic()
      await startMihomoConnections()
      await startMihomoLogs()
      await startMihomoMemory()
      retry = 10
    }
  })
}

// 启动核心
export async function startCore(detached = false, skipStop = false): Promise<Promise<void>[]> {
  const config = await prepareCore(detached, skipStop)
  child = spawnCoreProcess(config)

  if (detached) {
    managerLogger.info(
      `Core process detached successfully on ${process.platform}, PID: ${child.pid}`
    )
    child.unref()
    return [new Promise(() => {})]
  }

  return new Promise((resolve, reject) => {
    setupCoreListeners(child, config.logLevel, resolve, reject)
  })
}

// 停止核心
export async function stopCore(force = false): Promise<void> {
  try {
    if (!force) {
      await recoverDNS()
    }
  } catch (error) {
    managerLogger.error('recover dns failed', error)
  }

  if (child) {
    child.removeAllListeners()
    child.kill('SIGINT')
  }

  stopMihomoTraffic()
  stopMihomoConnections()
  stopMihomoLogs()
  stopMihomoMemory()

  try {
    await getAxios(true)
  } catch (error) {
    managerLogger.warn('Failed to refresh axios instance:', error)
  }

  await cleanupSocketFile()
}

// 重启核心
export async function restartCore(): Promise<void> {
  if (isRestarting) {
    managerLogger.info('Core restart already in progress, skipping duplicate request')
    return
  }

  isRestarting = true
  let retryCount = 0
  const maxRetries = 3

  try {
    // 先显式停止核心,确保状态干净
    await stopCore()

    // 尝试启动核心,失败时重试
    while (retryCount < maxRetries) {
      try {
        // skipStop=true 因为我们已经在上面停止了核心
        await startCore(false, true)
        return // 成功启动,退出函数
      } catch (e) {
        retryCount++
        managerLogger.error(`restart core failed (attempt ${retryCount}/${maxRetries})`, e)

        if (retryCount >= maxRetries) {
          throw e
        }

        // 重试前等待一段时间
        await new Promise((resolve) => setTimeout(resolve, 1000 * retryCount))
        // 确保清理干净再重试
        await stopCore()
        await cleanupSocketFile()
      }
    }
  } finally {
    isRestarting = false
  }
}

// 保持核心运行
export async function keepCoreAlive(): Promise<void> {
  try {
    await startCore(true)
    if (child?.pid) {
      await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString())
    }
  } catch (e) {
    safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
  }
}

// 退出但保持核心运行
export async function quitWithoutCore(): Promise<void> {
  managerLogger.info(`Starting lightweight mode on platform: ${process.platform}`)

  try {
    await startCore(true)
    if (child?.pid) {
      await writeFile(path.join(dataDir(), 'core.pid'), child.pid.toString())
      managerLogger.info(`Core started in lightweight mode with PID: ${child.pid}`)
    }
  } catch (e) {
    managerLogger.error('Failed to start core in lightweight mode:', e)
    safeShowErrorBox('mihomo.error.coreStartFailed', `${e}`)
  }

  await startMonitor(true)
  managerLogger.info('Exiting main process, core will continue running in background')
  app.exit()
}

// 检查配置文件
async function checkProfile(
  current: string | undefined,
  core: string = 'mihomo',
  diffWorkDir: boolean = false
): Promise<void> {
  const corePath = mihomoCorePath(core)

  try {
    await execFilePromise(corePath, [
      '-t',
      '-f',
      diffWorkDir ? mihomoWorkConfigPath(current) : mihomoWorkConfigPath('work'),
      '-d',
      mihomoTestDir()
    ])
  } catch (error) {
    managerLogger.error('Profile check failed', error)

    if (error instanceof Error && 'stdout' in error) {
      const { stdout, stderr } = error as { stdout: string; stderr?: string }
      managerLogger.info('Profile check stdout', stdout)
      managerLogger.info('Profile check stderr', stderr)

      const errorLines = stdout
        .split('\n')
        .filter((line) => line.includes('level=error') || line.includes('error'))
        .map((line) => {
          if (line.includes('level=error')) {
            return line.split('level=error')[1]?.trim() || line
          }
          return line.trim()
        })
        .filter((line) => line.length > 0)

      if (errorLines.length === 0) {
        const allLines = stdout.split('\n').filter((line) => line.trim().length > 0)
        throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}:\n${allLines.join('\n')}`)
      } else {
        throw new Error(
          `${i18next.t('mihomo.error.profileCheckFailed')}:\n${errorLines.join('\n')}`
        )
      }
    } else {
      throw new Error(`${i18next.t('mihomo.error.profileCheckFailed')}: ${error}`)
    }
  }
}

// 权限检查入口(从 permissions.ts 调用)
export async function checkAdminRestartForTun(): Promise<void> {
  const { checkAdminRestartForTun: check } = await import('./permissions')
  await check(restartCore)
}


================================================
FILE: src/main/core/mihomoApi.ts
================================================
import axios, { AxiosInstance } from 'axios'
import WebSocket from 'ws'
import { getAppConfig, getControledMihomoConfig } from '../config'
import { mainWindow } from '../window'
import { tray } from '../resolve
Download .txt
gitextract_lnqrk73k/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report_zh.yml
│   │   ├── config.yml
│   │   └── feature_request_zh.yml
│   └── workflows/
│       ├── build.yml
│       └── issues.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc.yaml
├── .vscode/
│   ├── extensions.json
│   ├── launch.json
│   └── settings.json
├── LICENSE
├── README.md
├── aur/
│   ├── mihomo-party/
│   │   ├── PKGBUILD
│   │   ├── mihomo-party.install
│   │   └── mihomo-party.sh
│   ├── mihomo-party-bin/
│   │   ├── PKGBUILD
│   │   ├── mihomo-party.install
│   │   └── mihomo-party.sh
│   ├── mihomo-party-electron/
│   │   ├── PKGBUILD
│   │   ├── mihomo-party.desktop
│   │   ├── mihomo-party.install
│   │   └── mihomo-party.sh
│   ├── mihomo-party-electron-bin/
│   │   ├── PKGBUILD
│   │   ├── mihomo-party.desktop
│   │   ├── mihomo-party.install
│   │   └── mihomo-party.sh
│   └── mihomo-party-git/
│       ├── PKGBUILD
│       ├── mihomo-party.install
│       └── mihomo-party.sh
├── build/
│   ├── entitlements.mac.plist
│   ├── icon.icns
│   ├── linux/
│   │   ├── postinst
│   │   └── postuninst
│   └── pkg-scripts/
│       ├── postinstall
│       └── preinstall
├── changelog.md
├── electron-builder.yml
├── electron.vite.config.ts
├── eslint.config.cjs
├── package.json
├── scripts/
│   ├── checksum.mjs
│   ├── cleanup-mac.sh
│   ├── copy-legacy-artifacts.mjs
│   ├── prepare.mjs
│   ├── telegram.mjs
│   ├── update-version.mjs
│   ├── updater.mjs
│   └── version-utils.mjs
├── src/
│   ├── main/
│   │   ├── config/
│   │   │   ├── app.ts
│   │   │   ├── controledMihomo.ts
│   │   │   ├── index.ts
│   │   │   ├── override.ts
│   │   │   ├── profile.ts
│   │   │   └── smartOverride.ts
│   │   ├── core/
│   │   │   ├── dns.ts
│   │   │   ├── factory.ts
│   │   │   ├── manager.ts
│   │   │   ├── mihomoApi.ts
│   │   │   ├── permissions.ts
│   │   │   ├── process.ts
│   │   │   ├── profileUpdater.ts
│   │   │   └── subStoreApi.ts
│   │   ├── deeplink.ts
│   │   ├── index.ts
│   │   ├── lifecycle.ts
│   │   ├── resolve/
│   │   │   ├── autoUpdater.ts
│   │   │   ├── backup.ts
│   │   │   ├── floatingWindow.ts
│   │   │   ├── gistApi.ts
│   │   │   ├── server.ts
│   │   │   ├── shortcut.ts
│   │   │   ├── theme.ts
│   │   │   ├── trafficMonitor.ts
│   │   │   └── tray.ts
│   │   ├── sys/
│   │   │   ├── autoRun.ts
│   │   │   ├── interface.ts
│   │   │   ├── misc.ts
│   │   │   ├── ssid.ts
│   │   │   └── sysproxy.ts
│   │   ├── utils/
│   │   │   ├── appName.ts
│   │   │   ├── calc.ts
│   │   │   ├── chromeRequest.ts
│   │   │   ├── defaultIcon.ts
│   │   │   ├── dirs.ts
│   │   │   ├── github.ts
│   │   │   ├── icon.ts
│   │   │   ├── image.ts
│   │   │   ├── init.ts
│   │   │   ├── ipc.ts
│   │   │   ├── logger.ts
│   │   │   ├── merge.ts
│   │   │   ├── template.ts
│   │   │   └── yaml.ts
│   │   └── window.ts
│   ├── native/
│   │   └── sysproxy/
│   │       ├── index.d.ts
│   │       ├── index.js
│   │       └── package.json
│   ├── preload/
│   │   ├── index.d.ts
│   │   └── index.ts
│   ├── renderer/
│   │   ├── floating.html
│   │   ├── index.html
│   │   └── src/
│   │       ├── App.tsx
│   │       ├── FloatingApp.tsx
│   │       ├── assets/
│   │       │   ├── floating.css
│   │       │   ├── hero.ts
│   │       │   └── main.css
│   │       ├── components/
│   │       │   ├── base/
│   │       │   │   ├── base-confirm-modal.tsx
│   │       │   │   ├── base-editor.tsx
│   │       │   │   ├── base-error-boundary.tsx
│   │       │   │   ├── base-page.tsx
│   │       │   │   ├── base-setting-card.tsx
│   │       │   │   ├── base-setting-item.tsx
│   │       │   │   ├── border-switch.css
│   │       │   │   ├── border-swtich.tsx
│   │       │   │   ├── collapse-input.tsx
│   │       │   │   ├── mihomo-icon.tsx
│   │       │   │   ├── substore-icon.tsx
│   │       │   │   └── toast.tsx
│   │       │   ├── connections/
│   │       │   │   ├── connection-detail-modal.tsx
│   │       │   │   ├── connection-item.tsx
│   │       │   │   └── connection-table.tsx
│   │       │   ├── logs/
│   │       │   │   └── log-item.tsx
│   │       │   ├── mihomo/
│   │       │   │   └── interface-modal.tsx
│   │       │   ├── override/
│   │       │   │   ├── edit-file-modal.tsx
│   │       │   │   ├── edit-info-modal.tsx
│   │       │   │   ├── exec-log-modal.tsx
│   │       │   │   └── override-item.tsx
│   │       │   ├── profiles/
│   │       │   │   ├── edit-file-modal.tsx
│   │       │   │   ├── edit-info-modal.tsx
│   │       │   │   ├── edit-rules-modal.tsx
│   │       │   │   └── profile-item.tsx
│   │       │   ├── proxies/
│   │       │   │   └── proxy-item.tsx
│   │       │   ├── resources/
│   │       │   │   ├── geo-data.tsx
│   │       │   │   ├── proxy-provider.tsx
│   │       │   │   ├── rule-provider.tsx
│   │       │   │   └── viewer.tsx
│   │       │   ├── rules/
│   │       │   │   └── rule-item.tsx
│   │       │   ├── settings/
│   │       │   │   ├── actions.tsx
│   │       │   │   ├── css-editor-modal.tsx
│   │       │   │   ├── general-config.tsx
│   │       │   │   ├── local-backup-config.tsx
│   │       │   │   ├── mihomo-config.tsx
│   │       │   │   ├── shortcut-config.tsx
│   │       │   │   ├── sider-config.tsx
│   │       │   │   ├── substore-config.tsx
│   │       │   │   ├── webdav-config.tsx
│   │       │   │   └── webdav-restore-modal.tsx
│   │       │   ├── sider/
│   │       │   │   ├── config-viewer.tsx
│   │       │   │   ├── conn-card.tsx
│   │       │   │   ├── dns-card.tsx
│   │       │   │   ├── log-card.tsx
│   │       │   │   ├── mihomo-core-card.tsx
│   │       │   │   ├── outbound-mode-switcher.tsx
│   │       │   │   ├── override-card.tsx
│   │       │   │   ├── profile-card.tsx
│   │       │   │   ├── proxy-card.tsx
│   │       │   │   ├── resource-card.tsx
│   │       │   │   ├── rule-card.tsx
│   │       │   │   ├── sniff-card.tsx
│   │       │   │   ├── substore-card.tsx
│   │       │   │   ├── sysproxy-switcher.tsx
│   │       │   │   └── tun-switcher.tsx
│   │       │   ├── sysproxy/
│   │       │   │   └── pac-editor-modal.tsx
│   │       │   └── updater/
│   │       │       ├── updater-button.tsx
│   │       │       └── updater-modal.tsx
│   │       ├── floating.tsx
│   │       ├── hooks/
│   │       │   ├── create-config-context.tsx
│   │       │   ├── use-app-config.tsx
│   │       │   ├── use-controled-mihomo-config.tsx
│   │       │   ├── use-groups.tsx
│   │       │   ├── use-override-config.tsx
│   │       │   ├── use-profile-config.tsx
│   │       │   └── use-rules.tsx
│   │       ├── i18n.ts
│   │       ├── locales/
│   │       │   ├── en-US.json
│   │       │   ├── fa-IR.json
│   │       │   ├── ru-RU.json
│   │       │   ├── zh-CN.json
│   │       │   └── zh-TW.json
│   │       ├── main.tsx
│   │       ├── pages/
│   │       │   ├── connections.tsx
│   │       │   ├── dns.tsx
│   │       │   ├── logs.tsx
│   │       │   ├── mihomo.tsx
│   │       │   ├── override.tsx
│   │       │   ├── profiles.tsx
│   │       │   ├── proxies.tsx
│   │       │   ├── resources.tsx
│   │       │   ├── rules.tsx
│   │       │   ├── settings.tsx
│   │       │   ├── sniffer.tsx
│   │       │   ├── substore.tsx
│   │       │   ├── sysproxy.tsx
│   │       │   └── tun.tsx
│   │       ├── routes/
│   │       │   └── index.tsx
│   │       └── utils/
│   │           ├── calc.ts
│   │           ├── dayjs.ts
│   │           ├── debounce.ts
│   │           ├── env.d.ts
│   │           ├── error-display.ts
│   │           ├── hash.ts
│   │           ├── icon-cache.ts
│   │           ├── image.ts
│   │           ├── includes.ts
│   │           ├── init.ts
│   │           ├── ipc.ts
│   │           ├── tour.ts
│   │           └── validate.ts
│   └── shared/
│       ├── i18n.ts
│       └── types.d.ts
├── tsconfig.json
├── tsconfig.node.json
└── tsconfig.web.json
Download .txt
SYMBOL INDEX (516 symbols across 119 files)

FILE: scripts/prepare.mjs
  constant TEMP_DIR (line 9) | const TEMP_DIR = path.join(cwd, 'node_modules/.temp')
  constant MIHOMO_ALPHA_VERSION_URL (line 17) | const MIHOMO_ALPHA_VERSION_URL =
  constant MIHOMO_ALPHA_URL_PREFIX (line 19) | const MIHOMO_ALPHA_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/rel...
  constant MIHOMO_ALPHA_VERSION (line 20) | let MIHOMO_ALPHA_VERSION
  constant MIHOMO_ALPHA_MAP (line 22) | const MIHOMO_ALPHA_MAP = {
  function getLatestAlphaVersion (line 33) | async function getLatestAlphaVersion() {
  constant MIHOMO_SMART_VERSION_URL (line 48) | const MIHOMO_SMART_VERSION_URL =
  constant MIHOMO_SMART_URL_PREFIX (line 50) | const MIHOMO_SMART_URL_PREFIX = `https://github.com/vernesong/mihomo/rel...
  constant MIHOMO_SMART_VERSION (line 51) | let MIHOMO_SMART_VERSION
  constant MIHOMO_SMART_MAP (line 53) | const MIHOMO_SMART_MAP = {
  function getLatestSmartVersion (line 63) | async function getLatestSmartVersion() {
  constant MIHOMO_VERSION_URL (line 78) | const MIHOMO_VERSION_URL =
  constant MIHOMO_URL_PREFIX (line 80) | const MIHOMO_URL_PREFIX = `https://github.com/MetaCubeX/mihomo/releases/...
  constant MIHOMO_VERSION (line 81) | let MIHOMO_VERSION
  constant MIHOMO_MAP (line 83) | const MIHOMO_MAP = {
  function getLatestReleaseVersion (line 94) | async function getLatestReleaseVersion() {
  function MihomoAlpha (line 126) | function MihomoAlpha() {
  function mihomo (line 143) | function mihomo() {
  function mihomoSmart (line 160) | function mihomoSmart() {
  function resolveSidecar (line 179) | async function resolveSidecar(binInfo) {
  function resolveResource (line 259) | async function resolveResource(binInfo) {
  function downloadFile (line 278) | async function downloadFile(url, path) {
  constant SYSPROXY_RS_VERSION (line 320) | const SYSPROXY_RS_VERSION = 'v0.1.0'
  constant SYSPROXY_RS_URL_PREFIX (line 321) | const SYSPROXY_RS_URL_PREFIX = `https://github.com/mihomo-party-org/sysp...
  function getSysproxyNodeName (line 323) | function getSysproxyNodeName() {
  function runTask (line 525) | async function runTask() {

FILE: scripts/telegram.mjs
  function convertMarkdownToTelegramHTML (line 19) | function convertMarkdownToTelegramHTML(content) {

FILE: scripts/update-version.mjs
  function updatePackageVersion (line 5) | function updatePackageVersion() {

FILE: scripts/version-utils.mjs
  function getGitCommitHash (line 5) | function getGitCommitHash(short = true) {
  function getCurrentMonthDate (line 16) | function getCurrentMonthDate() {
  function getBaseVersion (line 24) | function getBaseVersion() {
  function getDevVersion (line 37) | function getDevVersion() {
  function isDevBuild (line 46) | function isDevBuild() {
  function getProcessedVersion (line 55) | function getProcessedVersion() {
  function getDownloadUrl (line 64) | function getDownloadUrl(isDev, version) {
  function generateDownloadLinksMarkdown (line 72) | function generateDownloadLinksMarkdown(downloadUrl, version) {

FILE: src/main/config/app.ts
  function getAppConfig (line 10) | async function getAppConfig(force = false): Promise<IAppConfig> {
  function patchAppConfig (line 27) | async function patchAppConfig(patch: Partial<IAppConfig>): Promise<void> {

FILE: src/main/config/controledMihomo.ts
  function getControledMihomoConfig (line 16) | async function getControledMihomoConfig(force = false): Promise<Partial<...
  function patchControledMihomoConfig (line 53) | async function patchControledMihomoConfig(patch: Partial<IMihomoConfig>)...

FILE: src/main/config/override.ts
  function getOverrideConfig (line 11) | async function getOverrideConfig(force = false): Promise<IOverrideConfig> {
  function setOverrideConfig (line 21) | async function setOverrideConfig(config: IOverrideConfig): Promise<void> {
  function getOverrideItem (line 29) | async function getOverrideItem(id: string | undefined): Promise<IOverrid...
  function updateOverrideItem (line 34) | async function updateOverrideItem(item: IOverrideItem): Promise<void> {
  function addOverrideItem (line 44) | async function addOverrideItem(item: Partial<IOverrideItem>): Promise<vo...
  function removeOverrideItem (line 55) | async function removeOverrideItem(id: string): Promise<void> {
  function createOverride (line 66) | async function createOverride(item: Partial<IOverrideItem>): Promise<IOv...
  function getOverride (line 103) | async function getOverride(id: string, ext: 'js' | 'yaml' | 'log'): Prom...
  function setOverride (line 110) | async function setOverride(id: string, ext: 'js' | 'yaml', content: stri...

FILE: src/main/config/profile.ts
  function getProfileConfig (line 24) | async function getProfileConfig(force = false): Promise<IProfileConfig> {
  function setProfileConfig (line 34) | async function setProfileConfig(config: IProfileConfig): Promise<void> {
  function updateProfileConfig (line 42) | async function updateProfileConfig(
  function getProfileItem (line 59) | async function getProfileItem(id: string | undefined): Promise<IProfileI...
  function changeCurrentProfile (line 66) | async function changeCurrentProfile(id: string): Promise<void> {
  function updateProfileItem (line 96) | async function updateProfileItem(item: IProfileItem): Promise<void> {
  function addProfileItem (line 107) | async function addProfileItem(item: Partial<IProfileItem>): Promise<void> {
  function removeProfileItem (line 145) | async function removeProfileItem(id: string): Promise<void> {
  function getCurrentProfileItem (line 169) | async function getCurrentProfileItem(): Promise<IProfileItem> {
  type FetchOptions (line 180) | interface FetchOptions {
  type FetchResult (line 190) | interface FetchResult {
  function fetchAndValidateSubscription (line 195) | async function fetchAndValidateSubscription(options: FetchOptions): Prom...
  function createProfile (line 237) | async function createProfile(item: Partial<IProfileItem>): Promise<IProf...
  function getProfileStr (line 314) | async function getProfileStr(id: string | undefined): Promise<string> {
  function setProfileStr (line 322) | async function setProfileStr(id: string, content: string): Promise<void> {
  function getProfile (line 347) | async function getProfile(id: string | undefined): Promise<IMihomoConfig> {
  function parseFilename (line 374) | function parseFilename(str: string): string {
  function parseSubinfo (line 389) | function parseSubinfo(str: string): ISubscriptionUserInfo {
  function isAbsolutePath (line 399) | function isAbsolutePath(path: string): boolean {
  function getFileStr (line 403) | async function getFileStr(path: string): Promise<string> {
  function setFileStr (line 416) | async function setFileStr(path: string, content: string): Promise<void> {
  function convertMrsRuleset (line 430) | async function convertMrsRuleset(filePath: string, behavior: string): Pr...

FILE: src/main/config/smartOverride.ts
  constant SMART_OVERRIDE_ID (line 5) | const SMART_OVERRIDE_ID = 'smart-core-override'
  function generateSmartOverrideTemplate (line 10) | function generateSmartOverrideTemplate(
  function createSmartOverride (line 353) | async function createSmartOverride(): Promise<void> {
  function removeSmartOverride (line 388) | async function removeSmartOverride(): Promise<void> {
  function manageSmartOverride (line 403) | async function manageSmartOverride(): Promise<void> {
  function isSmartOverrideExists (line 416) | async function isSmartOverrideExists(): Promise<boolean> {

FILE: src/main/core/dns.ts
  function getDefaultDevice (line 13) | async function getDefaultDevice(): Promise<string> {
  function getDefaultService (line 21) | async function getDefaultService(): Promise<string> {
  function getOriginDNS (line 34) | async function getOriginDNS(): Promise<void> {
  function setDNS (line 44) | async function setDNS(dns: string): Promise<void> {
  function setPublicDNS (line 56) | async function setPublicDNS(): Promise<void> {
  function recoverDNS (line 70) | async function recoverDNS(): Promise<void> {

FILE: src/main/core/factory.ts
  function processRulesWithOffset (line 32) | function processRulesWithOffset(ruleStrings: string[], currentRules: str...
  function generateProfile (line 62) | async function generateProfile(): Promise<string | undefined> {
  function prepareProfileWorkDir (line 159) | async function prepareProfileWorkDir(current: string | undefined): Promi...
  function overrideProfile (line 192) | async function overrideProfile(
  function runOverrideScript (line 217) | function runOverrideScript(
  function getRuntimeConfigStr (line 260) | async function getRuntimeConfigStr(): Promise<string> {
  function getRuntimeConfig (line 264) | async function getRuntimeConfig(): Promise<IMihomoConfig> {

FILE: src/main/core/manager.ts
  function initCoreWatcher (line 83) | function initCoreWatcher(): void {
  function cleanupCoreWatcher (line 100) | function cleanupCoreWatcher(): void {
  type CoreConfig (line 125) | interface CoreConfig {
  function prepareCore (line 137) | async function prepareCore(detached: boolean, skipStop = false): Promise...
  function spawnCoreProcess (line 203) | function spawnCoreProcess(config: CoreConfig): ChildProcess {
  function setupCoreListeners (line 230) | function setupCoreListeners(
  function startCore (line 328) | async function startCore(detached = false, skipStop = false): Promise<Pr...
  function stopCore (line 346) | async function stopCore(force = false): Promise<void> {
  function restartCore (line 375) | async function restartCore(): Promise<void> {
  function keepCoreAlive (line 416) | async function keepCoreAlive(): Promise<void> {
  function quitWithoutCore (line 428) | async function quitWithoutCore(): Promise<void> {
  function checkProfile (line 448) | async function checkProfile(
  function checkAdminRestartForTun (line 497) | async function checkAdminRestartForTun(): Promise<void> {

FILE: src/main/core/mihomoApi.ts
  constant MAX_RETRY (line 25) | const MAX_RETRY = 10
  function mihomoVersion (line 63) | async function mihomoVersion(): Promise<IMihomoVersion> {
  function SysProxyStatus (line 447) | async function SysProxyStatus(): Promise<boolean> {
  function calculateTrayIconStatus (line 457) | function calculateTrayIconStatus(
  function getTrayIconStatus (line 472) | async function getTrayIconStatus(): Promise<'white' | 'blue' | 'green' |...

FILE: src/main/core/permissions.ts
  constant ALLOWED_CORES (line 16) | const ALLOWED_CORES = ['mihomo', 'mihomo-alpha', 'mihomo-smart'] as const
  type AllowedCore (line 17) | type AllowedCore = (typeof ALLOWED_CORES)[number]
  function isValidCoreName (line 19) | function isValidCoreName(core: string): core is AllowedCore {
  function validateCorePath (line 23) | function validateCorePath(corePath: string): void {
  function shellEscape (line 41) | function shellEscape(arg: string): string {
  function initAdminStatus (line 48) | async function initAdminStatus(): Promise<void> {
  function getSessionAdminStatus (line 54) | function getSessionAdminStatus(): boolean {
  function checkAdminPrivileges (line 61) | async function checkAdminPrivileges(): Promise<boolean> {
  function checkMihomoCorePermissions (line 88) | async function checkMihomoCorePermissions(): Promise<boolean> {
  function checkHighPrivilegeCore (line 108) | async function checkHighPrivilegeCore(): Promise<boolean> {
  function checkHighPrivilegeMihomoProcess (line 144) | async function checkHighPrivilegeMihomoProcess(): Promise<boolean> {
  function grantTunPermissions (line 230) | async function grantTunPermissions(): Promise<void> {
  function restartAsAdmin (line 256) | async function restartAsAdmin(forTun: boolean = true): Promise<void> {
  function requestTunPermissions (line 296) | async function requestTunPermissions(): Promise<void> {
  function showTunPermissionDialog (line 307) | async function showTunPermissionDialog(): Promise<boolean> {
  function showErrorDialog (line 330) | async function showErrorDialog(title: string, message: string): Promise<...
  function validateTunPermissionsOnStartup (line 342) | async function validateTunPermissionsOnStartup(
  function checkAdminRestartForTun (line 371) | async function checkAdminRestartForTun(restartCore: () => Promise<void>)...
  function checkTunPermissions (line 406) | function checkTunPermissions(): Promise<boolean> {
  function manualGrantCorePermition (line 410) | function manualGrantCorePermition(): Promise<void> {

FILE: src/main/core/process.ts
  constant CORE_READY_MAX_RETRIES (line 11) | const CORE_READY_MAX_RETRIES = 30
  constant CORE_READY_RETRY_INTERVAL_MS (line 12) | const CORE_READY_RETRY_INTERVAL_MS = 100
  function cleanupSocketFile (line 14) | async function cleanupSocketFile(): Promise<void> {
  function cleanupWindowsNamedPipes (line 22) | async function cleanupWindowsNamedPipes(): Promise<void> {
  function terminateProcess (line 58) | async function terminateProcess(pid: number): Promise<void> {
  function fallbackTextParsing (line 70) | async function fallbackTextParsing(stdout: string): Promise<void> {
  function cleanupUnixSockets (line 83) | async function cleanupUnixSockets(): Promise<void> {
  function validateWindowsPipeAccess (line 106) | async function validateWindowsPipeAccess(pipePath: string): Promise<void> {
  function waitForCoreReady (line 115) | async function waitForCoreReady(): Promise<void> {

FILE: src/main/core/profileUpdater.ts
  function updateProfile (line 8) | async function updateProfile(id: string): Promise<void> {
  function initProfileUpdater (line 15) | async function initProfileUpdater(): Promise<void> {
  function addProfileUpdater (line 94) | async function addProfileUpdater(item: IProfileItem): Promise<void> {
  function removeProfileUpdater (line 128) | async function removeProfileUpdater(id: string): Promise<void> {

FILE: src/main/core/subStoreApi.ts
  function subStoreSubs (line 5) | async function subStoreSubs(): Promise<ISubStoreSub[]> {
  function subStoreCollections (line 14) | async function subStoreCollections(): Promise<ISubStoreSub[]> {

FILE: src/main/deeplink.ts
  function handleDeepLink (line 7) | async function handleDeepLink(url: string): Promise<void> {

FILE: src/main/index.ts
  function initApp (line 48) | async function initApp(): Promise<void> {
  function checkHighPrivilegeCoreEarly (line 59) | async function checkHighPrivilegeCoreEarly(): Promise<void> {
  function initHardwareAcceleration (line 103) | async function initHardwareAcceleration(): Promise<void> {

FILE: src/main/lifecycle.ts
  function customRelaunch (line 10) | function customRelaunch(): void {
  function fixUserDataPermissions (line 23) | async function fixUserDataPermissions(): Promise<void> {
  function setupPlatformSpecifics (line 46) | function setupPlatformSpecifics(): void {
  function setupAppLifecycle (line 56) | function setupAppLifecycle(): void {
  function getSystemLanguage (line 73) | function getSystemLanguage(): 'zh-CN' | 'en-US' {

FILE: src/main/resolve/autoUpdater.ts
  function checkUpdate (line 18) | async function checkUpdate(): Promise<IAppVersion | undefined> {
  function compareVersions (line 42) | function compareVersions(a: string, b: string): number {
  function downloadAndInstallUpdate (line 59) | async function downloadAndInstallUpdate(version: string): Promise<void> {

FILE: src/main/resolve/backup.ts
  type WebDAVContext (line 25) | interface WebDAVContext {
  function getWebDAVClient (line 31) | async function getWebDAVClient(): Promise<WebDAVContext> {
  function createBackupZip (line 58) | function createBackupZip(): AdmZip {
  function webdavBackup (line 91) | async function webdavBackup(): Promise<boolean> {
  function webdavRestore (line 140) | async function webdavRestore(filename: string): Promise<void> {
  function listWebdavBackups (line 147) | async function listWebdavBackups(): Promise<string[]> {
  function webdavDelete (line 157) | async function webdavDelete(filename: string): Promise<void> {
  function initWebdavBackupScheduler (line 165) | async function initWebdavBackupScheduler(): Promise<void> {
  function stopWebdavBackupScheduler (line 199) | async function stopWebdavBackupScheduler(): Promise<void> {
  function reinitScheduler (line 211) | async function reinitScheduler(): Promise<void> {
  function exportLocalBackup (line 221) | async function exportLocalBackup(): Promise<boolean> {
  function importLocalBackup (line 246) | async function importLocalBackup(): Promise<boolean> {

FILE: src/main/resolve/floatingWindow.ts
  function logError (line 12) | function logError(message: string, error?: unknown): void {
  function createFloatingWindow (line 16) | async function createFloatingWindow(): Promise<void> {
  function showFloatingWindow (line 101) | async function showFloatingWindow(): Promise<void> {
  function triggerFloatingWindow (line 122) | async function triggerFloatingWindow(): Promise<void> {
  function closeFloatingWindow (line 132) | async function closeFloatingWindow(): Promise<void> {
  function showContextMenu (line 142) | async function showContextMenu(): Promise<void> {

FILE: src/main/resolve/gistApi.ts
  type GistInfo (line 5) | interface GistInfo {
  function listGists (line 11) | async function listGists(token: string): Promise<GistInfo[]> {
  function createGist (line 29) | async function createGist(token: string, content: string): Promise<void> {
  function updateGist (line 53) | async function updateGist(token: string, id: string, content: string): P...
  function getGistUrl (line 76) | async function getGistUrl(): Promise<string> {
  function uploadRuntimeConfig (line 92) | async function uploadRuntimeConfig(): Promise<void> {

FILE: src/main/resolve/server.ts
  function findAvailablePort (line 28) | function findAvailablePort(startPort: number): Promise<number> {
  function startPacServer (line 49) | async function startPacServer(): Promise<void> {
  function stopPacServer (line 69) | async function stopPacServer(): Promise<void> {
  function startSubStoreFrontendServer (line 75) | async function startSubStoreFrontendServer(): Promise<void> {
  function stopSubStoreFrontendServer (line 85) | async function stopSubStoreFrontendServer(): Promise<void> {
  function startSubStoreBackendServer (line 91) | async function startSubStoreBackendServer(): Promise<void> {
  function stopSubStoreBackendServer (line 137) | async function stopSubStoreBackendServer(): Promise<void> {
  function downloadSubStore (line 143) | async function downloadSubStore(): Promise<void> {

FILE: src/main/resolve/shortcut.ts
  function registerShortcut (line 16) | async function registerShortcut(
  function initShortcut (line 140) | async function initShortcut(): Promise<void> {

FILE: src/main/resolve/theme.ts
  function resolveThemes (line 15) | async function resolveThemes(): Promise<{ key: string; label: string }[]> {
  function fetchThemes (line 36) | async function fetchThemes(): Promise<void> {
  function importThemes (line 52) | async function importThemes(files: string[]): Promise<void> {
  function readTheme (line 62) | async function readTheme(theme: string): Promise<string> {
  function writeTheme (line 67) | async function writeTheme(theme: string, css: string): Promise<void> {
  function applyTheme (line 71) | async function applyTheme(theme: string): Promise<void> {

FILE: src/main/resolve/trafficMonitor.ts
  function startMonitor (line 10) | async function startMonitor(detached = false): Promise<void> {
  function stopMonitor (line 38) | async function stopMonitor(): Promise<void> {

FILE: src/main/resolve/tray.ts
  function createTray (line 375) | async function createTray(): Promise<void> {
  function updateTrayMenu (line 455) | async function updateTrayMenu(): Promise<void> {
  function copyEnv (line 463) | async function copyEnv(
  function showTrayIcon (line 505) | async function showTrayIcon(): Promise<void> {
  function closeTrayIcon (line 511) | async function closeTrayIcon(): Promise<void> {
  function showDockIcon (line 518) | async function showDockIcon(): Promise<void> {
  function hideDockIcon (line 524) | async function hideDockIcon(): Promise<void> {
  function updateTrayIconImmediate (line 548) | function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: b...
  function updateTrayIcon (line 575) | async function updateTrayIcon(): Promise<void> {

FILE: src/main/sys/autoRun.ts
  function getTaskXml (line 12) | function getTaskXml(asAdmin: boolean): string {
  function checkAutoRun (line 56) | async function checkAutoRun(): Promise<boolean> {
  function enableAutoRun (line 95) | async function enableAutoRun(): Promise<void> {
  function disableAutoRun (line 175) | async function disableAutoRun(): Promise<void> {

FILE: src/main/sys/interface.ts
  function getInterfaces (line 3) | function getInterfaces(): NodeJS.Dict<NetworkInterfaceInfo[]> {

FILE: src/main/sys/misc.ts
  function getFilePath (line 16) | function getFilePath(ext: string[]): string[] | undefined {
  function readTextFile (line 24) | async function readTextFile(filePath: string): Promise<string> {
  function openFile (line 28) | function openFile(type: 'profile' | 'override', id: string, ext?: 'yaml'...
  function openUWPTool (line 37) | async function openUWPTool(): Promise<void> {
  function setupFirewall (line 55) | async function setupFirewall(): Promise<void> {
  function setNativeTheme (line 77) | function setNativeTheme(theme: 'system' | 'light' | 'dark'): void {
  function resetAppConfig (line 81) | function resetAppConfig(): void {

FILE: src/main/sys/ssid.ts
  function getCurrentSSID (line 9) | async function getCurrentSSID(): Promise<string | undefined> {
  function checkSSID (line 37) | async function checkSSID(): Promise<void> {
  function startSSIDCheck (line 60) | async function startSSIDCheck(): Promise<void> {
  function stopSSIDCheck (line 68) | function stopSSIDCheck(): void {
  function getSSIDByAirport (line 75) | async function getSSIDByAirport(): Promise<string | undefined> {
  function getSSIDByNetworksetup (line 91) | async function getSSIDByNetworksetup(): Promise<string | undefined> {
  function getSSIDByNetsh (line 105) | async function getSSIDByNetsh(): Promise<string | undefined> {
  function getSSIDByIwconfig (line 116) | async function getSSIDByIwconfig(): Promise<string | undefined> {

FILE: src/main/sys/sysproxy.ts
  function triggerSysProxy (line 58) | async function triggerSysProxy(enable: boolean): Promise<void> {
  function enableSysProxy (line 72) | async function enableSysProxy(): Promise<void> {
  function disableSysProxy (line 113) | async function disableSysProxy(): Promise<void> {
  function isSocketFileExists (line 130) | function isSocketFileExists(): boolean {
  function isHelperRunning (line 138) | async function isHelperRunning(): Promise<boolean> {
  function startHelperService (line 148) | async function startHelperService(): Promise<void> {
  function requestSocketRecreation (line 156) | async function requestSocketRecreation(): Promise<void> {
  function helperRequest (line 169) | async function helperRequest(requestFn: () => Promise<unknown>, maxRetri...

FILE: src/main/utils/appName.ts
  function getAppName (line 7) | async function getAppName(appPath: string): Promise<string> {
  function getLocalizedAppName (line 43) | function getLocalizedAppName(appPath: string): string {

FILE: src/main/utils/calc.ts
  function calcTraffic (line 1) | function calcTraffic(byte: number): string {
  function formatNumString (line 21) | function formatNumString(num: number): string {

FILE: src/main/utils/chromeRequest.ts
  type RequestOptions (line 3) | interface RequestOptions {
  type Response (line 21) | interface Response<T = unknown> {
  function getProxySession (line 34) | async function getProxySession(proxyUrl: string): Promise<Electron.Sessi...
  function request (line 52) | async function request<T = unknown>(

FILE: src/main/utils/dirs.ts
  function isPortable (line 8) | function isPortable(): boolean {
  function dataDir (line 12) | function dataDir(): string {
  function taskDir (line 20) | function taskDir(): string {
  function subStoreDir (line 34) | function subStoreDir(): string {
  function exeDir (line 38) | function exeDir(): string {
  function exePath (line 42) | function exePath(): string {
  function resourcesDir (line 46) | function resourcesDir(): string {
  function resourcesFilesDir (line 58) | function resourcesFilesDir(): string {
  function themesDir (line 62) | function themesDir(): string {
  function mihomoCoreDir (line 66) | function mihomoCoreDir(): string {
  function mihomoCorePath (line 70) | function mihomoCorePath(core: string): string {
  function appConfigPath (line 79) | function appConfigPath(): string {
  function controledMihomoConfigPath (line 83) | function controledMihomoConfigPath(): string {
  function profileConfigPath (line 87) | function profileConfigPath(): string {
  function profilesDir (line 91) | function profilesDir(): string {
  function profilePath (line 95) | function profilePath(id: string): string {
  function overrideDir (line 99) | function overrideDir(): string {
  function overrideConfigPath (line 103) | function overrideConfigPath(): string {
  function overridePath (line 107) | function overridePath(id: string, ext: 'js' | 'yaml' | 'log'): string {
  function mihomoWorkDir (line 111) | function mihomoWorkDir(): string {
  function mihomoProfileWorkDir (line 115) | function mihomoProfileWorkDir(id: string | undefined): string {
  function mihomoTestDir (line 119) | function mihomoTestDir(): string {
  function mihomoWorkConfigPath (line 123) | function mihomoWorkConfigPath(id: string | undefined): string {
  function logDir (line 131) | function logDir(): string {
  function logPath (line 135) | function logPath(): string {
  function substoreLogPath (line 144) | function substoreLogPath(): string {
  function coreLogPath (line 153) | function coreLogPath(): string {
  function rulesDir (line 162) | function rulesDir(): string {
  function rulePath (line 166) | function rulePath(id: string): string {

FILE: src/main/utils/github.ts
  type GitHubTag (line 15) | interface GitHubTag {
  type VersionCache (line 21) | interface VersionCache {
  constant CACHE_EXPIRY (line 26) | const CACHE_EXPIRY = 5 * 60 * 1000
  constant GITHUB_API_CONFIG (line 28) | const GITHUB_API_CONFIG = {
  constant PLATFORM_MAP (line 34) | const PLATFORM_MAP: Record<string, string> = {
  function getGitHubTags (line 53) | async function getGitHubTags(
  function clearVersionCache (line 105) | function clearVersionCache(owner: string, repo: string): void {
  function downloadGitHubAsset (line 117) | async function downloadGitHubAsset(url: string, outputPath: string): Pro...
  function installMihomoCore (line 140) | async function installMihomoCore(version: string): Promise<void> {

FILE: src/main/utils/icon.ts
  function isIOSApp (line 12) | function isIOSApp(appPath: string): boolean {
  function hasIOSAppIcon (line 22) | function hasIOSAppIcon(appPath: string): boolean {
  function hasMacOSAppIcon (line 35) | function hasMacOSAppIcon(appPath: string): boolean {
  function findBestAppPath (line 49) | function findBestAppPath(appPath: string): string | null {
  function findDesktopFile (line 84) | async function findDesktopFile(appPath: string): Promise<string | null> {
  function parseIconNameFromDesktopFile (line 134) | function parseIconNameFromDesktopFile(content: string): string | null {
  function resolveIconPath (line 139) | function resolveIconPath(iconName: string): string | null {
  function getIconDataURL (line 173) | async function getIconDataURL(appPath: string): Promise<string> {
  function getImageDataURL (line 272) | async function getImageDataURL(url: string): Promise<string> {

FILE: src/main/utils/image.ts
  function getImageDataURL (line 4) | async function getImageDataURL(url: string): Promise<string> {

FILE: src/main/utils/init.ts
  function safeShowErrorBox (line 50) | function safeShowErrorBox(titleKey: string, message: string): void {
  function fixDataDirPermissions (line 63) | async function fixDataDirPermissions(): Promise<void> {
  function isSourceNewer (line 86) | async function isSourceNewer(sourcePath: string, targetPath: string): Pr...
  function initDirs (line 95) | async function initDirs(): Promise<void> {
  function initConfig (line 119) | async function initConfig(): Promise<void> {
  function killOldMihomoProcesses (line 141) | async function killOldMihomoProcesses(): Promise<void> {
  function initFiles (line 174) | async function initFiles(): Promise<void> {
  function cleanup (line 232) | async function cleanup(): Promise<void> {
  function migrateSubStoreFiles (line 258) | async function migrateSubStoreFiles(): Promise<void> {
  function migrateSiderOrder (line 272) | async function migrateSiderOrder(): Promise<void> {
  function migrateAppTheme (line 280) | async function migrateAppTheme(): Promise<void> {
  function migrateEnvType (line 288) | async function migrateEnvType(): Promise<void> {
  function migrateTraySettings (line 296) | async function migrateTraySettings(): Promise<void> {
  function migrateRemovePassword (line 304) | async function migrateRemovePassword(): Promise<void> {
  function migrateMihomoConfig (line 312) | async function migrateMihomoConfig(): Promise<void> {
  function migration (line 355) | async function migration(): Promise<void> {
  function initDeeplink (line 366) | function initDeeplink(): void {
  function initBasic (line 378) | async function initBasic(): Promise<void> {
  function init (line 391) | async function init(): Promise<void> {

FILE: src/main/utils/ipc.ts
  type AsyncFn (line 132) | type AsyncFn = (...args: any[]) => Promise<any>
  type SyncFn (line 134) | type SyncFn = (...args: any[]) => any
  function wrapAsync (line 136) | function wrapAsync<T extends AsyncFn>(
  function registerHandlers (line 151) | function registerHandlers(handlers: Record<string, AsyncFn | SyncFn>, as...
  function fetchMihomoTags (line 161) | async function fetchMihomoTags(
  function installSpecificMihomoCore (line 167) | async function installSpecificMihomoCore(version: string): Promise<void> {
  function clearMihomoVersionCache (line 172) | async function clearMihomoVersionCache(): Promise<void> {
  function getRuleStr (line 176) | async function getRuleStr(id: string): Promise<string> {
  function setRuleStr (line 180) | async function setRuleStr(id: string, str: string): Promise<void> {
  function getSmartOverrideContent (line 184) | async function getSmartOverrideContent(): Promise<string | null> {
  function changeLanguage (line 193) | async function changeLanguage(lng: string): Promise<void> {
  function setTitleBarOverlay (line 198) | async function setTitleBarOverlay(overlay: Electron.TitleBarOverlayOptio...
  function registerIpcMainHandlers (line 360) | function registerIpcMainHandlers(): void {

FILE: src/main/utils/logger.ts
  type LogLevel (line 4) | type LogLevel = 'debug' | 'info' | 'warn' | 'error'
  class Logger (line 6) | class Logger {
    method constructor (line 9) | constructor(moduleName: string) {
    method formatTimestamp (line 13) | private formatTimestamp(): string {
    method formatLogMessage (line 17) | private formatLogMessage(level: LogLevel, message: string, error?: unk...
    method writeToFile (line 23) | private async writeToFile(level: LogLevel, message: string, error?: un...
    method logToConsole (line 38) | private logToConsole(level: LogLevel, message: string, error?: unknown...
    method debug (line 57) | async debug(message: string, error?: unknown): Promise<void> {
    method info (line 62) | async info(message: string, error?: unknown): Promise<void> {
    method warn (line 67) | async warn(message: string, error?: unknown): Promise<void> {
    method error (line 72) | async error(message: string, error?: unknown): Promise<void> {
    method log (line 78) | async log(message: string, error?: unknown): Promise<void> {

FILE: src/main/utils/merge.ts
  function isObject (line 2) | function isObject(item: any): boolean {
  function trimWrap (line 6) | function trimWrap(str: string): string {
  function deepMerge (line 13) | function deepMerge<T extends object>(target: T, other: Partial<T>): T {

FILE: src/main/utils/yaml.ts
  function stringify (line 11) | function stringify(content: unknown): string {

FILE: src/main/window.ts
  function createWindow (line 14) | async function createWindow(): Promise<void> {
  type WindowConfig (line 72) | interface WindowConfig {
  function setupWindowEvents (line 78) | function setupWindowEvents(
  function scheduleQuitWithoutCore (line 148) | function scheduleQuitWithoutCore(delaySeconds: number): void {
  function clearQuitTimeout (line 155) | function clearQuitTimeout(): void {
  function triggerMainWindow (line 162) | function triggerMainWindow(force?: boolean): void {
  function showMainWindow (line 180) | function showMainWindow(): void {
  function closeMainWindow (line 188) | function closeMainWindow(): void {

FILE: src/native/sysproxy/index.d.ts
  type SysproxyInfo (line 1) | interface SysproxyInfo {
  type AutoproxyInfo (line 8) | interface AutoproxyInfo {

FILE: src/native/sysproxy/index.js
  function isWindows7 (line 10) | function isWindows7() {
  function isMusl (line 17) | function isMusl() {
  function getBindingName (line 33) | function getBindingName() {
  function getResourcesPath (line 60) | function getResourcesPath() {
  function loadBinding (line 89) | function loadBinding() {

FILE: src/preload/index.d.ts
  type IpcListener (line 3) | type IpcListener = (event: Electron.IpcRendererEvent, ...args: unknown[]...
  type SafeIpcRenderer (line 5) | interface SafeIpcRenderer {
  type ElectronAPI (line 13) | interface ElectronAPI {
  type Window (line 21) | interface Window {

FILE: src/preload/index.ts
  type InvokeChannel (line 172) | type InvokeChannel = (typeof validInvokeChannels)[number]
  type ListenChannel (line 173) | type ListenChannel = (typeof validListenChannels)[number]
  type SendChannel (line 174) | type SendChannel = (typeof validSendChannels)[number]
  type IpcListener (line 176) | type IpcListener = (event: Electron.IpcRendererEvent, ...args: unknown[]...

FILE: src/renderer/src/components/base/base-confirm-modal.tsx
  type Props (line 5) | interface Props {

FILE: src/renderer/src/components/base/base-editor.tsx
  type Language (line 9) | type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
  type Props (line 11) | interface Props {

FILE: src/renderer/src/components/base/base-error-boundary.tsx
  type Props (line 52) | interface Props {

FILE: src/renderer/src/components/base/base-page.tsx
  type Props (line 9) | interface Props {

FILE: src/renderer/src/components/base/base-setting-card.tsx
  type Props (line 5) | interface Props {

FILE: src/renderer/src/components/base/base-setting-item.tsx
  type Props (line 4) | interface Props {

FILE: src/renderer/src/components/base/border-swtich.tsx
  type BorderSwitchProps (line 5) | interface BorderSwitchProps extends Omit<SwitchProps, 'isSelected'> {

FILE: src/renderer/src/components/base/collapse-input.tsx
  type CollapseInputProps (line 5) | interface CollapseInputProps extends Omit<InputProps, 'onValueChange'> {

FILE: src/renderer/src/components/base/mihomo-icon.tsx
  function MihomoIcon (line 4) | function MihomoIcon(props: IconBaseProps): React.JSX.Element {

FILE: src/renderer/src/components/base/substore-icon.tsx
  function SubStoreIcon (line 4) | function SubStoreIcon(props: IconBaseProps): React.ReactElement {

FILE: src/renderer/src/components/base/toast.tsx
  type ToastType (line 6) | type ToastType = 'success' | 'error' | 'warning' | 'info'
  type ToastData (line 8) | interface ToastData {
  type ToastListener (line 18) | type ToastListener = (toasts: ToastData[]) => void

FILE: src/renderer/src/components/connections/connection-detail-modal.tsx
  type Props (line 20) | interface Props {

FILE: src/renderer/src/components/connections/connection-item.tsx
  type Props (line 7) | interface Props {

FILE: src/renderer/src/components/connections/connection-table.tsx
  type Props (line 8) | interface Props {
  type ColumnConfig (line 21) | interface ColumnConfig {
  constant DEFAULT_COLUMNS (line 32) | const DEFAULT_COLUMNS: Omit<ColumnConfig, 'label'>[] = [

FILE: src/renderer/src/components/mihomo/interface-modal.tsx
  type Props (line 14) | interface Props {

FILE: src/renderer/src/components/override/edit-file-modal.tsx
  type Props (line 8) | interface Props {

FILE: src/renderer/src/components/override/edit-info-modal.tsx
  type Props (line 16) | interface Props {

FILE: src/renderer/src/components/override/exec-log-modal.tsx
  type Props (line 14) | interface Props {

FILE: src/renderer/src/components/override/override-item.tsx
  type Props (line 23) | interface Props {
  type MenuItem (line 31) | interface MenuItem {

FILE: src/renderer/src/components/profiles/edit-file-modal.tsx
  type Props (line 8) | interface Props {

FILE: src/renderer/src/components/profiles/edit-info-modal.tsx
  type Props (line 26) | interface Props {

FILE: src/renderer/src/components/profiles/edit-rules-modal.tsx
  type Props (line 63) | interface Props {
  type RuleItem (line 68) | interface RuleItem {
  type RuleListItemProps (line 424) | interface RuleListItemProps {

FILE: src/renderer/src/components/profiles/profile-item.tsx
  type Props (line 27) | interface Props {
  type MenuItem (line 37) | interface MenuItem {

FILE: src/renderer/src/components/proxies/proxy-item.tsx
  type Props (line 7) | interface Props {
  function delayColor (line 18) | function delayColor(delay: number): 'primary' | 'success' | 'warning' | ...

FILE: src/renderer/src/components/resources/viewer.tsx
  type Language (line 7) | type Language = 'yaml' | 'javascript' | 'css' | 'json' | 'text'
  type Props (line 9) | interface Props {

FILE: src/renderer/src/components/rules/rule-item.tsx
  type RuleItemProps (line 6) | interface RuleItemProps extends IMihomoRulesDetail {

FILE: src/renderer/src/components/settings/css-editor-modal.tsx
  type Props (line 7) | interface Props {

FILE: src/renderer/src/components/settings/webdav-restore-modal.tsx
  type Props (line 8) | interface Props {

FILE: src/renderer/src/components/sider/config-viewer.tsx
  type Props (line 7) | interface Props {

FILE: src/renderer/src/components/sider/conn-card.tsx
  type Props (line 27) | interface Props {

FILE: src/renderer/src/components/sider/dns-card.tsx
  type Props (line 14) | interface Props {

FILE: src/renderer/src/components/sider/log-card.tsx
  type Props (line 10) | interface Props {

FILE: src/renderer/src/components/sider/mihomo-core-card.tsx
  type Props (line 16) | interface Props {

FILE: src/renderer/src/components/sider/override-card.tsx
  type Props (line 10) | interface Props {

FILE: src/renderer/src/components/sider/profile-card.tsx
  type Props (line 21) | interface Props {

FILE: src/renderer/src/components/sider/proxy-card.tsx
  type Props (line 11) | interface Props {

FILE: src/renderer/src/components/sider/resource-card.tsx
  type Props (line 10) | interface Props {

FILE: src/renderer/src/components/sider/rule-card.tsx
  type Props (line 11) | interface Props {

FILE: src/renderer/src/components/sider/sniff-card.tsx
  type Props (line 14) | interface Props {

FILE: src/renderer/src/components/sider/substore-card.tsx
  type Props (line 10) | interface Props {

FILE: src/renderer/src/components/sider/sysproxy-switcher.tsx
  type Props (line 14) | interface Props {

FILE: src/renderer/src/components/sider/tun-switcher.tsx
  type Props (line 13) | interface Props {

FILE: src/renderer/src/components/sysproxy/pac-editor-modal.tsx
  type Props (line 6) | interface Props {

FILE: src/renderer/src/components/updater/updater-button.tsx
  type Props (line 10) | interface Props {

FILE: src/renderer/src/components/updater/updater-modal.tsx
  type Props (line 16) | interface Props {

FILE: src/renderer/src/hooks/create-config-context.tsx
  type ConfigContextValue (line 6) | interface ConfigContextValue<T> {
  type CreateConfigContextOptions (line 11) | interface CreateConfigContextOptions<T> {
  function createConfigContext (line 17) | function createConfigContext<T>(options: CreateConfigContextOptions<T>) {
  type ActionOptions (line 48) | interface ActionOptions {
  function useConfigAction (line 53) | function useConfigAction<T>(

FILE: src/renderer/src/hooks/use-app-config.tsx
  type AppConfigContextType (line 13) | interface AppConfigContextType {

FILE: src/renderer/src/hooks/use-controled-mihomo-config.tsx
  type ControledMihomoConfigContextType (line 7) | interface ControledMihomoConfigContextType {

FILE: src/renderer/src/hooks/use-groups.tsx
  type GroupsContextType (line 5) | interface GroupsContextType {

FILE: src/renderer/src/hooks/use-override-config.tsx
  type OverrideConfigContextType (line 19) | interface OverrideConfigContextType {

FILE: src/renderer/src/hooks/use-profile-config.tsx
  type ProfileConfigContextType (line 20) | interface ProfileConfigContextType {

FILE: src/renderer/src/hooks/use-rules.tsx
  type RulesContextType (line 5) | interface RulesContextType {

FILE: src/renderer/src/pages/connections.tsx
  constant MAX_QUEUE_SIZE (line 45) | const MAX_QUEUE_SIZE = 100

FILE: src/renderer/src/pages/logs.tsx
  constant LOGS_FILTER_KEY (line 11) | const LOGS_FILTER_KEY = 'logs-filter'
  method clean (line 20) | clean(): void {

FILE: src/renderer/src/pages/mihomo.tsx
  type WebUIPanel (line 58) | interface WebUIPanel {

FILE: src/renderer/src/pages/proxies.tsx
  constant GROUP_EXPAND_STATE_KEY (line 25) | const GROUP_EXPAND_STATE_KEY = 'proxy_group_expand_state'

FILE: src/renderer/src/utils/calc.ts
  function calcTraffic (line 1) | function calcTraffic(byte: number): string {
  function formatNumString (line 21) | function formatNumString(num: number): string {
  function calcPercent (line 33) | function calcPercent(

FILE: src/renderer/src/utils/debounce.ts
  function debounce (line 2) | function debounce<T extends (...args: any[]) => void>(func: T, wait: num...

FILE: src/renderer/src/utils/error-display.ts
  constant DETAILED_ERROR_KEYWORDS (line 4) | const DETAILED_ERROR_KEYWORDS = [
  function shouldShowDetailedError (line 29) | function shouldShowDetailedError(message: string): boolean {
  function showError (line 35) | async function showError(error: unknown, title?: string): Promise<void> {
  function showErrorSync (line 46) | function showErrorSync(error: unknown, title?: string): void {

FILE: src/renderer/src/utils/hash.ts
  class HashType (line 3) | class HashType {
    method constructor (line 6) | constructor(hash: string) {
    method makeHash (line 10) | static makeHash(data: string): HashType {
    method equal (line 15) | equal(hash: HashType): boolean {
    method toString (line 19) | toString(): string {
    method isValid (line 23) | isValid(): boolean {
  function getHash (line 28) | function getHash(name: string): string {

FILE: src/renderer/src/utils/icon-cache.ts
  constant ICON_CACHE_MAX_SIZE (line 1) | const ICON_CACHE_MAX_SIZE = 500
  constant ICON_CACHE_KEY_PREFIX (line 2) | const ICON_CACHE_KEY_PREFIX = 'icon_'
  constant ICON_CACHE_INDEX_KEY (line 3) | const ICON_CACHE_INDEX_KEY = 'icon_cache_index'
  function saveIconToCache (line 5) | function saveIconToCache(path: string, dataURL: string): void {
  function getIconFromCache (line 43) | function getIconFromCache(path: string): string | null {
  function clearHalfIconCache (line 64) | function clearHalfIconCache(): void {

FILE: src/renderer/src/utils/image.ts
  function cropAndPadTransparent (line 1) | async function cropAndPadTransparent(

FILE: src/renderer/src/utils/includes.ts
  function includesIgnoreCase (line 1) | function includesIgnoreCase(mainStr: string = '', subStr: string = ''): ...

FILE: src/renderer/src/utils/init.ts
  function init (line 20) | async function init(): Promise<void> {

FILE: src/renderer/src/utils/ipc.ts
  function checkIpcError (line 3) | function checkIpcError<T>(response: unknown): T {
  function invoke (line 10) | async function invoke<T>(channel: string, ...args: unknown[]): Promise<T> {
  type IpcApi (line 16) | interface IpcApi {
  function applyTheme (line 323) | async function applyTheme(theme: string): Promise<void> {
  function setTitleBarOverlay (line 342) | async function setTitleBarOverlay(overlay: TitleBarOverlayOptions): Prom...
  function updateTrayIconImmediate (line 351) | function updateTrayIconImmediate(sysProxyEnabled: boolean, tunEnabled: b...
  function getAppName (line 356) | async function getAppName(appPath: string): Promise<string> {
  function getIconDataURL (line 361) | async function getIconDataURL(appPath: string): Promise<string> {

FILE: src/renderer/src/utils/tour.ts
  function getDriver (line 7) | function getDriver(): ReturnType<typeof driver> | null {
  function createTourDriver (line 11) | function createTourDriver(t: TFunction, navigate: NavigateFunction): void {
  function startTourIfNeeded (line 189) | function startTourIfNeeded(): void {

FILE: src/renderer/src/utils/validate.ts
  type ValidationResult (line 263) | interface ValidationResult {

FILE: src/shared/types.d.ts
  type OutboundMode (line 1) | type OutboundMode = 'rule' | 'global' | 'direct'
  type LogLevel (line 2) | type LogLevel = 'info' | 'debug' | 'warning' | 'error' | 'silent'
  type SysProxyMode (line 3) | type SysProxyMode = 'auto' | 'manual'
  type CardStatus (line 4) | type CardStatus = 'col-span-2' | 'col-span-1' | 'hidden'
  type AppTheme (line 5) | type AppTheme = 'system' | 'light' | 'dark'
  type MihomoGroupType (line 6) | type MihomoGroupType = 'Selector' | 'URLTest' | 'LoadBalance' | 'Relay'
  type Priority (line 7) | type Priority =
  type MihomoProxyType (line 14) | type MihomoProxyType =
  type TunStack (line 34) | type TunStack = 'gvisor' | 'mixed' | 'system'
  type FindProcessMode (line 35) | type FindProcessMode = 'off' | 'strict' | 'always'
  type DnsMode (line 36) | type DnsMode = 'normal' | 'fake-ip' | 'redir-host'
  type FilterMode (line 37) | type FilterMode = 'blacklist' | 'whitelist'
  type NetworkInterfaceInfo (line 38) | type NetworkInterfaceInfo = os.NetworkInterfaceInfo
  type IAppVersion (line 40) | interface IAppVersion {
  type IMihomoVersion (line 45) | interface IMihomoVersion {
  type IMihomoTrafficInfo (line 50) | interface IMihomoTrafficInfo {
  type IMihomoMemoryInfo (line 55) | interface IMihomoMemoryInfo {
  type IMihomoLogInfo (line 60) | interface IMihomoLogInfo {
  type IMihomoRulesInfo (line 66) | interface IMihomoRulesInfo {
  type IMihomoRulesDetail (line 70) | interface IMihomoRulesDetail {
  type IMihomoConnectionsInfo (line 85) | interface IMihomoConnectionsInfo {
  type IMihomoConnectionDetail (line 92) | interface IMihomoConnectionDetail {
  type IMihomoHistory (line 131) | interface IMihomoHistory {
  type IMihomoGroupDelay (line 136) | type IMihomoGroupDelay = Record<string, number>
  type IMihomoDelay (line 138) | interface IMihomoDelay {
  type IMihomoProxy (line 143) | interface IMihomoProxy {
  type IMihomoGroup (line 157) | interface IMihomoGroup {
  type IMihomoProxies (line 174) | interface IMihomoProxies {
  type IMihomoMixedGroup (line 178) | interface IMihomoMixedGroup extends IMihomoGroup {
  type IMihomoRuleProviders (line 182) | interface IMihomoRuleProviders {
  type IMihomoRuleProvider (line 186) | interface IMihomoRuleProvider {
  type IMihomoProxyProviders (line 196) | interface IMihomoProxyProviders {
  type ISubscriptionUserInfoUpper (line 200) | interface ISubscriptionUserInfoUpper {
  type IMihomoProxyProvider (line 207) | interface IMihomoProxyProvider {
  type ISysProxyConfig (line 218) | interface ISysProxyConfig {
  type IAppConfig (line 226) | interface IAppConfig {
  type IMihomoTunConfig (line 342) | interface IMihomoTunConfig {
  type IMihomoDNSConfig (line 372) | interface IMihomoDNSConfig {
  type IMihomoSnifferConfig (line 396) | interface IMihomoSnifferConfig {
  type IMihomoProfileConfig (line 419) | interface IMihomoProfileConfig {
  type IMihomoConfig (line 424) | interface IMihomoConfig {
  type IProfileConfig (line 465) | interface IProfileConfig {
  type IOverrideItem (line 470) | interface IOverrideItem {
  type IOverrideConfig (line 481) | interface IOverrideConfig {
  type ISubscriptionUserInfo (line 485) | interface ISubscriptionUserInfo {
  type IProfileItem (line 492) | interface IProfileItem {
  type ISubStoreSub (line 511) | interface ISubStoreSub {
Condensed preview — 216 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (3,603K chars).
[
  {
    "path": ".editorconfig",
    "chars": 146,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nindent_style = space\nindent_size = 2\nend_of_line = lf\ninsert_final_newline = true\ntrim_"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report_zh.yml",
    "chars": 1228,
    "preview": "name: 错误反馈\ndescription: '提交 clash-party 漏洞'\ntitle: '[Bug] '\nbody:\n  - type: checkboxes\n    id: ensure\n    attributes:\n  "
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "chars": 233,
    "preview": "blank_issues_enabled: false\ncontact_links:\n  - name: '常见问题'\n    about: '提出问题前请先查看常见问题'\n    url: 'https://clashparty.org/"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request_zh.yml",
    "chars": 742,
    "preview": "name: 功能请求\ndescription: '请求 clash-party 功能'\ntitle: '[Feature] '\nbody:\n  - type: checkboxes\n    id: ensure\n    attributes"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 24449,
    "preview": "name: Build\non:\n  push:\n    tags:\n      - v*\n    paths-ignore:\n      - 'README.md'\n      - '.github/ISSUE_TEMPLATE/**'\n "
  },
  {
    "path": ".github/workflows/issues.yml",
    "chars": 888,
    "preview": "name: Review Issues\n\non:\n  issues:\n    types: [opened]\n\njobs:\n  review:\n    runs-on: ubuntu-latest\n    steps:\n      - na"
  },
  {
    "path": ".gitignore",
    "chars": 136,
    "preview": "node_modules\nresources/files\nresources/sidecar\nextra\ndist\nout\n.DS_Store\n*.log*\n.idea\n*.ttf\nparty.md\nCLAUDE.md\ntsconfig.n"
  },
  {
    "path": ".npmrc",
    "chars": 200,
    "preview": "shamefully-hoist=true\nvirtual-store-dir-max-length=80\npublic-hoist-pattern[]=*@heroui/*\nonly-built-dependencies[]=electr"
  },
  {
    "path": ".prettierignore",
    "chars": 65,
    "preview": "out\ndist\npnpm-lock.yaml\nLICENSE.md\ntsconfig.json\ntsconfig.*.json\n"
  },
  {
    "path": ".prettierrc.yaml",
    "chars": 66,
    "preview": "singleQuote: true\nsemi: false\nprintWidth: 100\ntrailingComma: none\n"
  },
  {
    "path": ".vscode/extensions.json",
    "chars": 123,
    "preview": "{\n  \"recommendations\": [\n    \"dbaeumer.vscode-eslint\",\n    \"esbenp.prettier-vscode\",\n    \"bradlc.vscode-tailwindcss\"\n  ]"
  },
  {
    "path": ".vscode/launch.json",
    "chars": 915,
    "preview": "{\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"name\": \"Debug Main Process\",\n      \"type\": \"node\",\n      \"req"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 302,
    "preview": "{\n  \"[typescript]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[javascript]\": {\n    \"editor.defau"
  },
  {
    "path": "LICENSE",
    "chars": 35149,
    "preview": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free "
  },
  {
    "path": "README.md",
    "chars": 1542,
    "preview": "<h3 align=\"center\">\n  <img height='48px' src='./images/icon-white.png#gh-dark-mode-only'>\n  <img height='48px' src='./im"
  },
  {
    "path": "aur/mihomo-party/PKGBUILD",
    "chars": 1714,
    "preview": "pkgname=mihomo-party\npkgver=0.1.3\npkgrel=1\npkgdesc=\"Another Mihomo GUI.\"\narch=('x86_64' 'aarch64')\nurl=\"https://github.c"
  },
  {
    "path": "aur/mihomo-party/mihomo-party.install",
    "chars": 391,
    "preview": "# Colored makepkg-like functions\nnote() {\n    printf \"${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\\n\" \"$1\"\n}\n\n_all_"
  },
  {
    "path": "aur/mihomo-party/mihomo-party.sh",
    "chars": 409,
    "preview": "#!/usr/bin/bash\n\nXDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}\n\n# Allow users to override command-line options\nif [[ -f "
  },
  {
    "path": "aur/mihomo-party-bin/PKGBUILD",
    "chars": 1684,
    "preview": "pkgname=mihomo-party-bin\n_pkgname=mihomo-party\npkgver=0.1.3\npkgrel=1\npkgdesc=\"Another Mihomo GUI.\"\narch=('x86_64' 'aarch"
  },
  {
    "path": "aur/mihomo-party-bin/mihomo-party.install",
    "chars": 391,
    "preview": "# Colored makepkg-like functions\nnote() {\n    printf \"${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\\n\" \"$1\"\n}\n\n_all_"
  },
  {
    "path": "aur/mihomo-party-bin/mihomo-party.sh",
    "chars": 409,
    "preview": "#!/usr/bin/bash\n\nXDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}\n\n# Allow users to override command-line options\nif [[ -f "
  },
  {
    "path": "aur/mihomo-party-electron/PKGBUILD",
    "chars": 2070,
    "preview": "pkgname=mihomo-party-electron\n_pkgname=mihomo-party\npkgver=0.1.3\npkgrel=1\npkgdesc=\"Another Mihomo GUI.\"\narch=('x86_64' '"
  },
  {
    "path": "aur/mihomo-party-electron/mihomo-party.desktop",
    "chars": 229,
    "preview": "[Desktop Entry]\nName=Clash Party\nExec=mihomo-party %U\nTerminal=false\nType=Application\nIcon=mihomo-party\nStartupWMClass=m"
  },
  {
    "path": "aur/mihomo-party-electron/mihomo-party.install",
    "chars": 391,
    "preview": "# Colored makepkg-like functions\nnote() {\n    printf \"${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\\n\" \"$1\"\n}\n\n_all_"
  },
  {
    "path": "aur/mihomo-party-electron/mihomo-party.sh",
    "chars": 405,
    "preview": "#!/usr/bin/bash\n\nXDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}\n\n# Allow users to override command-line options\nif [[ -f "
  },
  {
    "path": "aur/mihomo-party-electron-bin/PKGBUILD",
    "chars": 2106,
    "preview": "pkgname=mihomo-party-electron-bin\n_pkgname=mihomo-party\npkgver=0.2.2\npkgrel=1\npkgdesc=\"Another Mihomo GUI.\"\narch=('x86_6"
  },
  {
    "path": "aur/mihomo-party-electron-bin/mihomo-party.desktop",
    "chars": 229,
    "preview": "[Desktop Entry]\nName=Clash Party\nExec=mihomo-party %U\nTerminal=false\nType=Application\nIcon=mihomo-party\nStartupWMClass=m"
  },
  {
    "path": "aur/mihomo-party-electron-bin/mihomo-party.install",
    "chars": 391,
    "preview": "# Colored makepkg-like functions\nnote() {\n    printf \"${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\\n\" \"$1\"\n}\n\n_all_"
  },
  {
    "path": "aur/mihomo-party-electron-bin/mihomo-party.sh",
    "chars": 405,
    "preview": "#!/usr/bin/bash\n\nXDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}\n\n# Allow users to override command-line options\nif [[ -f "
  },
  {
    "path": "aur/mihomo-party-git/PKGBUILD",
    "chars": 1905,
    "preview": "pkgname=mihomo-party-git\n_pkgname=${pkgname%-git}\npkgver=0.1.3.r5.g5f5d6dd\npkgrel=1\npkgdesc=\"Another Mihomo GUI.\"\narch=("
  },
  {
    "path": "aur/mihomo-party-git/mihomo-party.install",
    "chars": 391,
    "preview": "# Colored makepkg-like functions\nnote() {\n    printf \"${_blue}==>${_yellow} NOTE:${_bold} %s${_all_off}\\n\" \"$1\"\n}\n\n_all_"
  },
  {
    "path": "aur/mihomo-party-git/mihomo-party.sh",
    "chars": 409,
    "preview": "#!/usr/bin/bash\n\nXDG_CONFIG_HOME=${XDG_CONFIG_HOME:-~/.config}\n\n# Allow users to override command-line options\nif [[ -f "
  },
  {
    "path": "build/entitlements.mac.plist",
    "chars": 491,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "build/linux/postinst",
    "chars": 1489,
    "preview": "#!/bin/bash\n\nset -e\n\nif type update-alternatives >/dev/null 2>&1; then\n    # Remove previous link if it doesn't use upda"
  },
  {
    "path": "build/linux/postuninst",
    "chars": 804,
    "preview": "#!/bin/bash\n\ncase \"$1\" in\n  remove|purge|0)\n    if type update-alternatives >/dev/null 2>&1; then\n        update-alterna"
  },
  {
    "path": "build/pkg-scripts/postinstall",
    "chars": 5560,
    "preview": "#!/bin/bash\nset -e\n\n# 设置日志文件\nLOG_FILE=\"/tmp/mihomo-party-install.log\"\nexec > \"$LOG_FILE\" 2>&1\n\nlog() {\n    echo \"$(date "
  },
  {
    "path": "build/pkg-scripts/preinstall",
    "chars": 604,
    "preview": "#!/bin/bash\nset -e\n\n# 检查 root 权限\nif [ \"$EUID\" -ne 0 ]; then\n    echo \"Please run as root\"\n    exit 1\nfi\n\nHELPER_PATH=\"/L"
  },
  {
    "path": "changelog.md",
    "chars": 1292,
    "preview": "# 1.9.2\n\n## 新功能 (Feat)\n\n- 增加 Fish 和 Nushell 环境变量支持\n- 增加规则统计和禁用开关\n- 连接页面显示应用图标\n- 增加订阅超时设置\n- 增加 Windows 7 兼容构建支持\n\n## 修复 (F"
  },
  {
    "path": "electron-builder.yml",
    "chars": 2461,
    "preview": "appId: party.mihomo.app\nproductName: Clash Party\ndirectories:\n  buildResources: build\nfiles:\n  - '!**/.vscode/*'\n  - '!s"
  },
  {
    "path": "electron.vite.config.ts",
    "chars": 2094,
    "preview": "import { resolve } from 'path'\nimport { defineConfig, externalizeDepsPlugin } from 'electron-vite'\nimport react from '@v"
  },
  {
    "path": "eslint.config.cjs",
    "chars": 2073,
    "preview": "const js = require('@eslint/js')\nconst react = require('eslint-plugin-react')\nconst reactHooks = require('eslint-plugin-"
  },
  {
    "path": "package.json",
    "chars": 4464,
    "preview": "{\n  \"name\": \"mihomo-party\",\n  \"version\": \"1.9.2\",\n  \"description\": \"Clash Party\",\n  \"type\": \"module\",\n  \"main\": \"./out/m"
  },
  {
    "path": "scripts/checksum.mjs",
    "chars": 434,
    "preview": "import { readFileSync, readdirSync, writeFileSync } from 'fs'\nimport { createHash } from 'crypto'\nconst files = readdirS"
  },
  {
    "path": "scripts/cleanup-mac.sh",
    "chars": 958,
    "preview": "#!/bin/bash\n\necho \"=== Clash Party Cleanup Tool ===\"\necho \"This script will remove all Clash Party related files and ser"
  },
  {
    "path": "scripts/copy-legacy-artifacts.mjs",
    "chars": 1848,
    "preview": "import { readFileSync, readdirSync, writeFileSync, copyFileSync, existsSync } from 'fs'\nimport { join } from 'path'\n\n/**"
  },
  {
    "path": "scripts/prepare.mjs",
    "chars": 16652,
    "preview": "import fs from 'fs'\nimport AdmZip from 'adm-zip'\nimport path from 'path'\nimport zlib from 'zlib'\nimport { extract } from"
  },
  {
    "path": "scripts/telegram.mjs",
    "chars": 2695,
    "preview": "import axios from 'axios'\nimport { readFileSync } from 'fs'\nimport {\n  getProcessedVersion,\n  isDevBuild,\n  getDownloadU"
  },
  {
    "path": "scripts/update-version.mjs",
    "chars": 880,
    "preview": "import { readFileSync, writeFileSync } from 'fs'\nimport { getProcessedVersion, isDevBuild } from './version-utils.mjs'\n\n"
  },
  {
    "path": "scripts/updater.mjs",
    "chars": 722,
    "preview": "import yaml from 'yaml'\nimport { readFileSync, writeFileSync } from 'fs'\nimport {\n  getProcessedVersion,\n  isDevBuild,\n "
  },
  {
    "path": "scripts/version-utils.mjs",
    "chars": 3511,
    "preview": "import { execSync } from 'child_process'\nimport { readFileSync } from 'fs'\n\n// 获取Git commit hash\nexport function getGitC"
  },
  {
    "path": "src/main/config/app.ts",
    "chars": 1372,
    "preview": "import { readFile, writeFile } from 'fs/promises'\nimport { appConfigPath } from '../utils/dirs'\nimport { parse, stringif"
  },
  {
    "path": "src/main/config/controledMihomo.ts",
    "chars": 3396,
    "preview": "import { readFile, writeFile } from 'fs/promises'\nimport { existsSync } from 'fs'\nimport { controledMihomoConfigPath } f"
  },
  {
    "path": "src/main/config/index.ts",
    "chars": 777,
    "preview": "export { getAppConfig, patchAppConfig } from './app'\nexport { getControledMihomoConfig, patchControledMihomoConfig } fro"
  },
  {
    "path": "src/main/config/override.ts",
    "chars": 3775,
    "preview": "import { readFile, writeFile, rm } from 'fs/promises'\nimport { existsSync } from 'fs'\nimport { overrideConfigPath, overr"
  },
  {
    "path": "src/main/config/profile.ts",
    "chars": 15224,
    "preview": "import { readFile, rm, writeFile } from 'fs/promises'\nimport { existsSync } from 'fs'\nimport { join } from 'path'\nimport"
  },
  {
    "path": "src/main/config/smartOverride.ts",
    "chars": 14297,
    "preview": "import { overrideLogger } from '../utils/logger'\nimport { getAppConfig } from './app'\nimport { addOverrideItem, removeOv"
  },
  {
    "path": "src/main/core/dns.ts",
    "chars": 2883,
    "preview": "import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport { net } from 'electron'\nimport axios from '"
  },
  {
    "path": "src/main/core/factory.ts",
    "chars": 7660,
    "preview": "import { copyFile, mkdir, writeFile, readFile, stat } from 'fs/promises'\nimport vm from 'vm'\nimport { existsSync, writeF"
  },
  {
    "path": "src/main/core/manager.ts",
    "chars": 13221,
    "preview": "import { ChildProcess, execFile, spawn } from 'child_process'\nimport { readFile, rm, writeFile } from 'fs/promises'\nimpo"
  },
  {
    "path": "src/main/core/mihomoApi.ts",
    "chars": 14287,
    "preview": "import axios, { AxiosInstance } from 'axios'\nimport WebSocket from 'ws'\nimport { getAppConfig, getControledMihomoConfig "
  },
  {
    "path": "src/main/core/permissions.ts",
    "chars": 13588,
    "preview": "import { exec, execFile } from 'child_process'\nimport { promisify } from 'util'\nimport { stat } from 'fs/promises'\nimpor"
  },
  {
    "path": "src/main/core/process.ts",
    "chars": 4190,
    "preview": "import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport { rm } from 'fs/promises'\nimport { existsSy"
  },
  {
    "path": "src/main/core/profileUpdater.ts",
    "chars": 4459,
    "preview": "import { Cron } from 'croner'\nimport { addProfileItem, getCurrentProfileItem, getProfileConfig, getProfileItem } from '."
  },
  {
    "path": "src/main/core/subStoreApi.ts",
    "chars": 934,
    "preview": "import * as chromeRequest from '../utils/chromeRequest'\nimport { subStorePort } from '../resolve/server'\nimport { getApp"
  },
  {
    "path": "src/main/deeplink.ts",
    "chars": 1077,
    "preview": "import { Notification } from 'electron'\nimport i18next from 'i18next'\nimport { addProfileItem } from './config'\nimport {"
  },
  {
    "path": "src/main/index.ts",
    "chars": 5658,
    "preview": "import { electronApp, optimizer } from '@electron-toolkit/utils'\nimport { app, dialog } from 'electron'\nimport i18next f"
  },
  {
    "path": "src/main/lifecycle.ts",
    "chars": 1981,
    "preview": "import { spawn, exec } from 'child_process'\nimport { promisify } from 'util'\nimport { stat } from 'fs/promises'\nimport {"
  },
  {
    "path": "src/main/resolve/autoUpdater.ts",
    "chars": 7459,
    "preview": "import { copyFile, rm, writeFile } from 'fs/promises'\nimport path from 'path'\nimport { existsSync } from 'fs'\nimport os "
  },
  {
    "path": "src/main/resolve/backup.ts",
    "chars": 7447,
    "preview": "import https from 'https'\nimport { existsSync } from 'fs'\nimport dayjs from 'dayjs'\nimport AdmZip from 'adm-zip'\nimport "
  },
  {
    "path": "src/main/resolve/floatingWindow.ts",
    "chars": 4526,
    "preview": "import { join } from 'path'\nimport { is } from '@electron-toolkit/utils'\nimport { BrowserWindow, ipcMain } from 'electro"
  },
  {
    "path": "src/main/resolve/gistApi.ts",
    "chars": 3056,
    "preview": "import * as chromeRequest from '../utils/chromeRequest'\nimport { getAppConfig, getControledMihomoConfig } from '../confi"
  },
  {
    "path": "src/main/resolve/server.ts",
    "chars": 6767,
    "preview": "import { Worker } from 'worker_threads'\nimport { createWriteStream, existsSync, mkdirSync } from 'fs'\nimport { writeFile"
  },
  {
    "path": "src/main/resolve/shortcut.ts",
    "chars": 6387,
    "preview": "import { app, globalShortcut, ipcMain, Notification } from 'electron'\nimport { mainWindow, triggerMainWindow } from '../"
  },
  {
    "path": "src/main/resolve/theme.ts",
    "chars": 2876,
    "preview": "import { copyFile, readdir, readFile, writeFile } from 'fs/promises'\nimport path from 'path'\nimport { existsSync } from "
  },
  {
    "path": "src/main/resolve/trafficMonitor.ts",
    "chars": 1275,
    "preview": "import { ChildProcess, spawn } from 'child_process'\nimport path from 'path'\nimport { existsSync } from 'fs'\nimport { rea"
  },
  {
    "path": "src/main/resolve/tray.ts",
    "chars": 17744,
    "preview": "import { app, clipboard, ipcMain, Menu, nativeImage, shell, Tray } from 'electron'\nimport { t } from 'i18next'\nimport {\n"
  },
  {
    "path": "src/main/sys/autoRun.ts",
    "chars": 7159,
    "preview": "import { tmpdir } from 'os'\nimport { mkdir, readFile, rm, writeFile } from 'fs/promises'\nimport { exec } from 'child_pro"
  },
  {
    "path": "src/main/sys/interface.ts",
    "chars": 126,
    "preview": "import os from 'os'\n\nexport function getInterfaces(): NodeJS.Dict<NetworkInterfaceInfo[]> {\n  return os.networkInterface"
  },
  {
    "path": "src/main/sys/misc.ts",
    "chars": 3420,
    "preview": "import { exec, execFile, spawn } from 'child_process'\nimport { readFile } from 'fs/promises'\nimport path from 'path'\nimp"
  },
  {
    "path": "src/main/sys/ssid.ts",
    "chars": 3641,
    "preview": "import { exec } from 'child_process'\nimport { promisify } from 'util'\nimport { ipcMain, net } from 'electron'\nimport { g"
  },
  {
    "path": "src/main/sys/sysproxy.ts",
    "chars": 6561,
    "preview": "import { promisify } from 'util'\nimport { exec } from 'child_process'\nimport fs from 'fs'\nimport { triggerAutoProxy, tri"
  },
  {
    "path": "src/main/utils/appName.ts",
    "chars": 1866,
    "preview": "import fs from 'fs'\nimport path from 'path'\nimport { spawnSync } from 'child_process'\nimport plist from 'plist'\nimport {"
  },
  {
    "path": "src/main/utils/calc.ts",
    "chars": 888,
    "preview": "export function calcTraffic(byte: number): string {\n  if (byte < 1024) return `${byte} B`\n  byte /= 1024\n  if (byte < 10"
  },
  {
    "path": "src/main/utils/chromeRequest.ts",
    "chars": 7592,
    "preview": "import { net, session } from 'electron'\n\nexport interface RequestOptions {\n  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' "
  },
  {
    "path": "src/main/utils/defaultIcon.ts",
    "chars": 2374176,
    "preview": "export const windowsDefaultIcon =\n  'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAYAAABccqhmAAAAAXNSR0IArs4c6"
  },
  {
    "path": "src/main/utils/dirs.ts",
    "chars": 4314,
    "preview": "import { existsSync, mkdirSync } from 'fs'\nimport path from 'path'\nimport { is } from '@electron-toolkit/utils'\nimport {"
  },
  {
    "path": "src/main/utils/github.ts",
    "chars": 6619,
    "preview": "import { createWriteStream, createReadStream, existsSync, rmSync } from 'fs'\nimport { writeFile } from 'fs/promises'\nimp"
  },
  {
    "path": "src/main/utils/icon.ts",
    "chars": 8354,
    "preview": "import { exec } from 'child_process'\nimport fs, { existsSync } from 'fs'\nimport os from 'os'\nimport path from 'path'\nimp"
  },
  {
    "path": "src/main/utils/image.ts",
    "chars": 572,
    "preview": "import { getControledMihomoConfig } from '../config'\nimport * as chromeRequest from './chromeRequest'\n\nexport async func"
  },
  {
    "path": "src/main/utils/init.ts",
    "chars": 11723,
    "preview": "import { mkdir, writeFile, rm, readdir, cp, stat, rename } from 'fs/promises'\nimport { existsSync } from 'fs'\nimport { e"
  },
  {
    "path": "src/main/utils/ipc.ts",
    "chars": 9063,
    "preview": "import path from 'path'\nimport v8 from 'v8'\nimport { readFile, writeFile } from 'fs/promises'\nimport { app, ipcMain } fr"
  },
  {
    "path": "src/main/utils/logger.ts",
    "chars": 3475,
    "preview": "import { writeFile } from 'fs/promises'\nimport { logPath } from './dirs'\n\nexport type LogLevel = 'debug' | 'info' | 'war"
  },
  {
    "path": "src/main/utils/merge.ts",
    "chars": 1379,
    "preview": "// eslint-disable-next-line @typescript-eslint/no-explicit-any\nfunction isObject(item: any): boolean {\n  return item && "
  },
  {
    "path": "src/main/utils/template.ts",
    "chars": 4482,
    "preview": "export const defaultConfig: IAppConfig = {\n  core: 'mihomo',\n  enableSmartCore: false,\n  enableSmartOverride: true,\n  sm"
  },
  {
    "path": "src/main/utils/yaml.ts",
    "chars": 395,
    "preview": "import yaml from 'yaml'\n\nexport const parse = <T = unknown>(content: string): T => {\n  const processedContent = content."
  },
  {
    "path": "src/main/window.ts",
    "chars": 4549,
    "preview": "import { join } from 'path'\nimport { BrowserWindow, Menu, shell } from 'electron'\nimport { is } from '@electron-toolkit/"
  },
  {
    "path": "src/native/sysproxy/index.d.ts",
    "chars": 562,
    "preview": "export interface SysproxyInfo {\n  enable: boolean\n  host: string\n  port: number\n  bypass: string\n}\n\nexport interface Aut"
  },
  {
    "path": "src/native/sysproxy/index.js",
    "chars": 3511,
    "preview": "const { existsSync } = require('fs')\nconst { join, dirname } = require('path')\nconst os = require('os')\n\nconst { platfor"
  },
  {
    "path": "src/native/sysproxy/package.json",
    "chars": 192,
    "preview": "{\n  \"name\": \"sysproxy-rs\",\n  \"version\": \"0.4.0\",\n  \"description\": \"System proxy library for Node.js\",\n  \"type\": \"commonj"
  },
  {
    "path": "src/preload/index.d.ts",
    "chars": 657,
    "preview": "import { webUtils } from 'electron'\n\ntype IpcListener = (event: Electron.IpcRendererEvent, ...args: unknown[]) => void\n\n"
  },
  {
    "path": "src/preload/index.ts",
    "chars": 5834,
    "preview": "import { contextBridge, ipcRenderer, webUtils } from 'electron'\n\n// 允许的 invoke channels 白名单\nconst validInvokeChannels = "
  },
  {
    "path": "src/renderer/floating.html",
    "chars": 498,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" lang=\"zh\" />\n    <title>Clash Party Floating</title>\n    <!-- "
  },
  {
    "path": "src/renderer/index.html",
    "chars": 485,
    "preview": "<!doctype html>\n<html>\n  <head>\n    <meta charset=\"UTF-8\" lang=\"zh\" />\n    <title>Clash Party</title>\n    <!-- https://d"
  },
  {
    "path": "src/renderer/src/App.tsx",
    "chars": 10103,
    "preview": "import { useTheme } from 'next-themes'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { Navigat"
  },
  {
    "path": "src/renderer/src/FloatingApp.tsx",
    "chars": 3553,
    "preview": "import { useEffect, useMemo, useState, useCallback } from 'react'\nimport MihomoIcon from './components/base/mihomo-icon'"
  },
  {
    "path": "src/renderer/src/assets/floating.css",
    "chars": 473,
    "preview": "@import 'tailwindcss';\n@plugin './hero.ts';\n\n@source '../**/*.{js,ts,jsx,tsx}';\n@source '../../../../node_modules/@herou"
  },
  {
    "path": "src/renderer/src/assets/hero.ts",
    "chars": 63,
    "preview": "import { heroui } from '@heroui/react'\nexport default heroui()\n"
  },
  {
    "path": "src/renderer/src/assets/main.css",
    "chars": 3596,
    "preview": "@import 'tailwindcss';\n@plugin './hero.ts';\n\n@source '../**/*.{js,ts,jsx,tsx}';\n@source '../../../../node_modules/@herou"
  },
  {
    "path": "src/renderer/src/components/base/base-confirm-modal.tsx",
    "chars": 1067,
    "preview": "import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'\nimport React from 'reac"
  },
  {
    "path": "src/renderer/src/components/base/base-editor.tsx",
    "chars": 4569,
    "preview": "import React, { useEffect, useRef } from 'react'\nimport * as monaco from 'monaco-editor'\nimport MonacoEditor from 'react"
  },
  {
    "path": "src/renderer/src/components/base/base-error-boundary.tsx",
    "chars": 1547,
    "preview": "import { Button } from '@heroui/react'\nimport { ReactNode } from 'react'\nimport { ErrorBoundary, FallbackProps } from 'r"
  },
  {
    "path": "src/renderer/src/components/base/base-page.tsx",
    "chars": 2819,
    "preview": "import { Button, Divider } from '@heroui/react'\nimport { useAppConfig } from '@renderer/hooks/use-app-config'\nimport { p"
  },
  {
    "path": "src/renderer/src/components/base/base-setting-card.tsx",
    "chars": 1286,
    "preview": "import React, { useState } from 'react'\nimport { Accordion, AccordionItem, Card, CardBody } from '@heroui/react'\nimport "
  },
  {
    "path": "src/renderer/src/components/base/base-setting-item.tsx",
    "chars": 700,
    "preview": "import { Divider } from '@heroui/react'\nimport React from 'react'\n\ninterface Props {\n  title: React.ReactNode\n  actions?"
  },
  {
    "path": "src/renderer/src/components/base/border-switch.css",
    "chars": 97,
    "preview": ".border-switch {\n  overflow: hidden;\n}\n\n.border-switch input[type='checkbox'] {\n  width: 100%;\n}\n"
  },
  {
    "path": "src/renderer/src/components/base/border-swtich.tsx",
    "chars": 824,
    "preview": "import React from 'react'\nimport { cn, Switch, SwitchProps } from '@heroui/react'\nimport './border-switch.css'\n\ninterfac"
  },
  {
    "path": "src/renderer/src/components/base/collapse-input.tsx",
    "chars": 2313,
    "preview": "import React, { useRef, useState, useCallback } from 'react'\nimport { Input, InputProps } from '@heroui/react'\nimport { "
  },
  {
    "path": "src/renderer/src/components/base/mihomo-icon.tsx",
    "chars": 1823,
    "preview": "import React from 'react'\nimport { GenIcon, IconBaseProps } from 'react-icons'\n\nfunction MihomoIcon(props: IconBaseProps"
  },
  {
    "path": "src/renderer/src/components/base/substore-icon.tsx",
    "chars": 4181,
    "preview": "import { GenIcon, IconBaseProps } from 'react-icons'\nimport React from 'react'\n\nfunction SubStoreIcon(props: IconBasePro"
  },
  {
    "path": "src/renderer/src/components/base/toast.tsx",
    "chars": 7937,
    "preview": "import React, { useEffect, useState, useCallback } from 'react'\nimport { createPortal } from 'react-dom'\nimport { IoChec"
  },
  {
    "path": "src/renderer/src/components/connections/connection-detail-modal.tsx",
    "chars": 11318,
    "preview": "import {\n  Modal,\n  ModalContent,\n  ModalHeader,\n  ModalBody,\n  ModalFooter,\n  Button,\n  Dropdown,\n  DropdownTrigger,\n  "
  },
  {
    "path": "src/renderer/src/components/connections/connection-item.tsx",
    "chars": 5807,
    "preview": "import { Avatar, Button, Card, CardFooter, CardHeader, Chip } from '@heroui/react'\nimport { calcTraffic } from '@rendere"
  },
  {
    "path": "src/renderer/src/components/connections/connection-table.tsx",
    "chars": 14842,
    "preview": "import React, { useMemo, useRef, useState, useCallback } from 'react'\nimport { Button, Chip } from '@heroui/react'\nimpor"
  },
  {
    "path": "src/renderer/src/components/logs/log-item.tsx",
    "chars": 793,
    "preview": "import { Card, CardBody, CardHeader } from '@heroui/react'\nimport React from 'react'\n\nconst colorMap = {\n  error: 'dange"
  },
  {
    "path": "src/renderer/src/components/mihomo/interface-modal.tsx",
    "chars": 1858,
    "preview": "import {\n  Modal,\n  ModalContent,\n  ModalHeader,\n  ModalBody,\n  ModalFooter,\n  Button,\n  Snippet\n} from '@heroui/react'\n"
  },
  {
    "path": "src/renderer/src/components/override/edit-file-modal.tsx",
    "chars": 2303,
    "preview": "import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'\nimport { toast } from '"
  },
  {
    "path": "src/renderer/src/components/override/edit-info-modal.tsx",
    "chars": 2451,
    "preview": "import {\n  Modal,\n  ModalContent,\n  ModalHeader,\n  ModalBody,\n  ModalFooter,\n  Button,\n  Input,\n  Switch\n} from '@heroui"
  },
  {
    "path": "src/renderer/src/components/override/exec-log-modal.tsx",
    "chars": 1469,
    "preview": "import {\n  Modal,\n  ModalContent,\n  ModalHeader,\n  ModalBody,\n  ModalFooter,\n  Button,\n  Divider\n} from '@heroui/react'\n"
  },
  {
    "path": "src/renderer/src/components/override/override-item.tsx",
    "chars": 7581,
    "preview": "import {\n  Button,\n  Card,\n  CardBody,\n  Chip,\n  Dropdown,\n  DropdownItem,\n  DropdownMenu,\n  DropdownTrigger\n} from '@he"
  },
  {
    "path": "src/renderer/src/components/profiles/edit-file-modal.tsx",
    "chars": 2432,
    "preview": "import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'\nimport React, { useEffe"
  },
  {
    "path": "src/renderer/src/components/profiles/edit-info-modal.tsx",
    "chars": 10774,
    "preview": "import {\n  cn,\n  Modal,\n  ModalContent,\n  ModalHeader,\n  ModalBody,\n  ModalFooter,\n  Button,\n  Input,\n  Switch,\n  Dropdo"
  },
  {
    "path": "src/renderer/src/components/profiles/edit-rules-modal.tsx",
    "chars": 40774,
    "preview": "import {\n  Modal,\n  ModalContent,\n  ModalHeader,\n  ModalBody,\n  ModalFooter,\n  Button,\n  Chip,\n  Input,\n  Select,\n  Sele"
  },
  {
    "path": "src/renderer/src/components/profiles/profile-item.tsx",
    "chars": 11774,
    "preview": "import {\n  Button,\n  Card,\n  CardBody,\n  CardFooter,\n  Chip,\n  Dropdown,\n  DropdownItem,\n  DropdownMenu,\n  DropdownTrigg"
  },
  {
    "path": "src/renderer/src/components/proxies/proxy-item.tsx",
    "chars": 6355,
    "preview": "import { Button, Card, CardBody } from '@heroui/react'\nimport { mihomoUnfixedProxy } from '@renderer/utils/ipc'\nimport R"
  },
  {
    "path": "src/renderer/src/components/resources/geo-data.tsx",
    "chars": 5807,
    "preview": "import { Button, Input, Switch, Tab, Tabs } from '@heroui/react'\nimport { toast } from '@renderer/components/base/toast'"
  },
  {
    "path": "src/renderer/src/components/resources/proxy-provider.tsx",
    "chars": 6166,
    "preview": "import {\n  mihomoProxyProviders,\n  mihomoUpdateProxyProviders,\n  getRuntimeConfig\n} from '@renderer/utils/ipc'\nimport { "
  },
  {
    "path": "src/renderer/src/components/resources/rule-provider.tsx",
    "chars": 5865,
    "preview": "import {\n  mihomoRuleProviders,\n  mihomoUpdateRuleProviders,\n  getRuntimeConfig\n} from '@renderer/utils/ipc'\nimport { ge"
  },
  {
    "path": "src/renderer/src/components/resources/viewer.tsx",
    "chars": 4024,
    "preview": "import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'\nimport React, { useEffe"
  },
  {
    "path": "src/renderer/src/components/rules/rule-item.tsx",
    "chars": 3461,
    "preview": "import { Card, CardBody, Chip, Switch } from '@heroui/react'\nimport React, { useState, useEffect } from 'react'\nimport {"
  },
  {
    "path": "src/renderer/src/components/settings/actions.tsx",
    "chars": 4604,
    "preview": "import { Button, Tooltip } from '@heroui/react'\nimport { toast } from '@renderer/components/base/toast'\nimport {\n  check"
  },
  {
    "path": "src/renderer/src/components/settings/css-editor-modal.tsx",
    "chars": 1685,
    "preview": "import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'\nimport { BaseEditor } f"
  },
  {
    "path": "src/renderer/src/components/settings/general-config.tsx",
    "chars": 20455,
    "preview": "import React, { useEffect, useState } from 'react'\nimport { toast } from '@renderer/components/base/toast'\nimport { Butt"
  },
  {
    "path": "src/renderer/src/components/settings/local-backup-config.tsx",
    "chars": 2954,
    "preview": "import React, { useState } from 'react'\nimport { toast } from '@renderer/components/base/toast'\nimport { Button, useDisc"
  },
  {
    "path": "src/renderer/src/components/settings/mihomo-config.tsx",
    "chars": 10212,
    "preview": "import React, { useState } from 'react'\nimport { toast } from '@renderer/components/base/toast'\nimport { Button, Input, "
  },
  {
    "path": "src/renderer/src/components/settings/shortcut-config.tsx",
    "chars": 7355,
    "preview": "import { Button, Input } from '@heroui/react'\nimport { toast } from '@renderer/components/base/toast'\nimport { useAppCon"
  },
  {
    "path": "src/renderer/src/components/settings/sider-config.tsx",
    "chars": 2859,
    "preview": "import SettingCard from '@renderer/components/base/base-setting-card'\nimport SettingItem from '@renderer/components/base"
  },
  {
    "path": "src/renderer/src/components/settings/substore-config.tsx",
    "chars": 9190,
    "preview": "import React, { useState } from 'react'\nimport SettingCard from '@renderer/components/base/base-setting-card'\nimport { t"
  },
  {
    "path": "src/renderer/src/components/settings/webdav-config.tsx",
    "chars": 7439,
    "preview": "import React, { useState } from 'react'\nimport { toast } from '@renderer/components/base/toast'\nimport { Button, Input, "
  },
  {
    "path": "src/renderer/src/components/settings/webdav-restore-modal.tsx",
    "chars": 2962,
    "preview": "import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'\nimport { toast } from '"
  },
  {
    "path": "src/renderer/src/components/sider/config-viewer.tsx",
    "chars": 1400,
    "preview": "import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'\nimport React, { useEffe"
  },
  {
    "path": "src/renderer/src/components/sider/conn-card.tsx",
    "chars": 15277,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport { FaCircleArrowDown, FaCircleArrowUp "
  },
  {
    "path": "src/renderer/src/components/sider/dns-card.tsx",
    "chars": 3729,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport { toast } from '@renderer/components/"
  },
  {
    "path": "src/renderer/src/components/sider/log-card.tsx",
    "chars": 2984,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport { IoJournalOutline } from 'react-icon"
  },
  {
    "path": "src/renderer/src/components/sider/mihomo-core-card.tsx",
    "chars": 5747,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport { toast } from '@renderer/components/"
  },
  {
    "path": "src/renderer/src/components/sider/outbound-mode-switcher.tsx",
    "chars": 1864,
    "preview": "import { Tabs, Tab } from '@heroui/react'\nimport { useAppConfig } from '@renderer/hooks/use-app-config'\nimport { useCont"
  },
  {
    "path": "src/renderer/src/components/sider/override-card.tsx",
    "chars": 3037,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport React from 'react'\nimport { MdFormatO"
  },
  {
    "path": "src/renderer/src/components/sider/profile-card.tsx",
    "chars": 9485,
    "preview": "import { Button, Card, CardBody, CardFooter, Chip, Progress, Tooltip } from '@heroui/react'\nimport { useProfileConfig } "
  },
  {
    "path": "src/renderer/src/components/sider/proxy-card.tsx",
    "chars": 3564,
    "preview": "import { Button, Card, CardBody, CardFooter, Chip, Tooltip } from '@heroui/react'\nimport { useSortable } from '@dnd-kit/"
  },
  {
    "path": "src/renderer/src/components/sider/resource-card.tsx",
    "chars": 3050,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport React from 'react'\nimport { useLocati"
  },
  {
    "path": "src/renderer/src/components/sider/rule-card.tsx",
    "chars": 3637,
    "preview": "import { Button, Card, CardBody, CardFooter, Chip, Tooltip } from '@heroui/react'\nimport { MdOutlineAltRoute } from 'rea"
  },
  {
    "path": "src/renderer/src/components/sider/sniff-card.tsx",
    "chars": 3794,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport { toast } from '@renderer/components/"
  },
  {
    "path": "src/renderer/src/components/sider/substore-card.tsx",
    "chars": 3106,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport { useLocation, useNavigate } from 're"
  },
  {
    "path": "src/renderer/src/components/sider/sysproxy-switcher.tsx",
    "chars": 4246,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport { toast } from '@renderer/components/"
  },
  {
    "path": "src/renderer/src/components/sider/tun-switcher.tsx",
    "chars": 5929,
    "preview": "import { Button, Card, CardBody, CardFooter, Tooltip } from '@heroui/react'\nimport { useControledMihomoConfig } from '@r"
  },
  {
    "path": "src/renderer/src/components/sysproxy/pac-editor-modal.tsx",
    "chars": 1514,
    "preview": "import { Modal, ModalContent, ModalHeader, ModalBody, ModalFooter, Button } from '@heroui/react'\nimport { BaseEditor } f"
  },
  {
    "path": "src/renderer/src/components/updater/updater-button.tsx",
    "chars": 1791,
    "preview": "import { Button } from '@heroui/react'\nimport { useAppConfig } from '@renderer/hooks/use-app-config'\nimport { checkUpdat"
  },
  {
    "path": "src/renderer/src/components/updater/updater-modal.tsx",
    "chars": 4139,
    "preview": "import {\n  Button,\n  Code,\n  Modal,\n  ModalBody,\n  ModalContent,\n  ModalFooter,\n  ModalHeader\n} from '@heroui/react'\nimp"
  },
  {
    "path": "src/renderer/src/floating.tsx",
    "chars": 987,
    "preview": "import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport { ThemeProvider as NextThemesProvider } from 'n"
  },
  {
    "path": "src/renderer/src/hooks/create-config-context.tsx",
    "chars": 1959,
    "preview": "import React, { createContext, useContext, ReactNode, useCallback, useEffect } from 'react'\nimport { useTranslation } fr"
  },
  {
    "path": "src/renderer/src/hooks/use-app-config.tsx",
    "chars": 1780,
    "preview": "import React, { ReactNode, useCallback } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { showError "
  },
  {
    "path": "src/renderer/src/hooks/use-controled-mihomo-config.tsx",
    "chars": 2070,
    "preview": "import React, { createContext, useContext, ReactNode } from 'react'\nimport { useTranslation } from 'react-i18next'\nimpor"
  },
  {
    "path": "src/renderer/src/hooks/use-groups.tsx",
    "chars": 1256,
    "preview": "import React, { createContext, useContext, ReactNode } from 'react'\nimport useSWR from 'swr'\nimport { mihomoGroups } fro"
  },
  {
    "path": "src/renderer/src/hooks/use-override-config.tsx",
    "chars": 2989,
    "preview": "import React, { ReactNode, useCallback } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { showError "
  },
  {
    "path": "src/renderer/src/hooks/use-profile-config.tsx",
    "chars": 4491,
    "preview": "import React, { ReactNode, useCallback, useRef } from 'react'\nimport { useTranslation } from 'react-i18next'\nimport { sh"
  },
  {
    "path": "src/renderer/src/hooks/use-rules.tsx",
    "chars": 1118,
    "preview": "import React, { createContext, useContext, ReactNode } from 'react'\nimport useSWR from 'swr'\nimport { mihomoRules } from"
  },
  {
    "path": "src/renderer/src/i18n.ts",
    "chars": 421,
    "preview": "import { initReactI18next } from 'react-i18next'\nimport i18n, { initI18n } from '../../shared/i18n'\nimport { getAppConfi"
  },
  {
    "path": "src/renderer/src/locales/en-US.json",
    "chars": 38530,
    "preview": "{\n  \"common.settings\": \"Settings\",\n  \"common.profiles\": \"Profiles\",\n  \"common.proxies\": \"Proxies\",\n  \"common.connections"
  },
  {
    "path": "src/renderer/src/locales/fa-IR.json",
    "chars": 36659,
    "preview": "{\n  \"common.settings\": \"تنظیمات\",\n  \"common.profiles\": \"پروفایل‌ها\",\n  \"common.proxies\": \"پراکسی‌ها\",\n  \"common.connecti"
  },
  {
    "path": "src/renderer/src/locales/ru-RU.json",
    "chars": 39168,
    "preview": "{\n  \"common.settings\": \"Настройки\",\n  \"common.profiles\": \"Профили\",\n  \"common.proxies\": \"Прокси\",\n  \"common.connections\""
  },
  {
    "path": "src/renderer/src/locales/zh-CN.json",
    "chars": 28627,
    "preview": "{\n  \"common.settings\": \"设置\",\n  \"common.profiles\": \"配置\",\n  \"common.proxies\": \"代理\",\n  \"common.connections\": \"连接\",\n  \"commo"
  },
  {
    "path": "src/renderer/src/locales/zh-TW.json",
    "chars": 28655,
    "preview": "{\n  \"common.settings\": \"設置\",\n  \"common.profiles\": \"配置\",\n  \"common.proxies\": \"代理\",\n  \"common.connections\": \"連接\",\n  \"commo"
  },
  {
    "path": "src/renderer/src/main.tsx",
    "chars": 2493,
    "preview": "import React from 'react'\nimport ReactDOM from 'react-dom/client'\nimport { HashRouter } from 'react-router-dom'\nimport {"
  },
  {
    "path": "src/renderer/src/pages/connections.tsx",
    "chars": 25397,
    "preview": "import BasePage from '@renderer/components/base/base-page'\nimport {\n  mihomoCloseAllConnections,\n  mihomoCloseConnection"
  },
  {
    "path": "src/renderer/src/pages/dns.tsx",
    "chars": 16900,
    "preview": "import { Button, Tab, Input, Switch, Tabs, Divider } from '@heroui/react'\nimport BasePage from '@renderer/components/bas"
  },
  {
    "path": "src/renderer/src/pages/logs.tsx",
    "chars": 3489,
    "preview": "import BasePage from '@renderer/components/base/base-page'\nimport LogItem from '@renderer/components/logs/log-item'\nimpo"
  },
  {
    "path": "src/renderer/src/pages/mihomo.tsx",
    "chars": 60921,
    "preview": "import {\n  Button,\n  Divider,\n  Input,\n  Select,\n  SelectItem,\n  Switch,\n  Tooltip,\n  Modal,\n  ModalContent,\n  ModalHead"
  },
  {
    "path": "src/renderer/src/pages/override.tsx",
    "chars": 8951,
    "preview": "import {\n  Button,\n  Divider,\n  Dropdown,\n  DropdownItem,\n  DropdownMenu,\n  DropdownTrigger,\n  Input\n} from '@heroui/rea"
  },
  {
    "path": "src/renderer/src/pages/profiles.tsx",
    "chars": 16660,
    "preview": "import {\n  Button,\n  Checkbox,\n  Chip,\n  Divider,\n  Dropdown,\n  DropdownItem,\n  DropdownMenu,\n  DropdownTrigger,\n  Input"
  },
  {
    "path": "src/renderer/src/pages/proxies.tsx",
    "chars": 19588,
    "preview": "import { Avatar, Button, Card, CardBody, Chip } from '@heroui/react'\nimport BasePage from '@renderer/components/base/bas"
  },
  {
    "path": "src/renderer/src/pages/resources.tsx",
    "chars": 559,
    "preview": "import BasePage from '@renderer/components/base/base-page'\nimport GeoData from '@renderer/components/resources/geo-data'"
  },
  {
    "path": "src/renderer/src/pages/rules.tsx",
    "chars": 1725,
    "preview": "import BasePage from '@renderer/components/base/base-page'\nimport RuleItem from '@renderer/components/rules/rule-item'\ni"
  },
  {
    "path": "src/renderer/src/pages/settings.tsx",
    "chars": 2359,
    "preview": "import { Button } from '@heroui/react'\nimport BasePage from '@renderer/components/base/base-page'\nimport { CgWebsite } f"
  },
  {
    "path": "src/renderer/src/pages/sniffer.tsx",
    "chars": 8671,
    "preview": "import { Button, Divider, Input, Switch } from '@heroui/react'\nimport BasePage from '@renderer/components/base/base-page"
  },
  {
    "path": "src/renderer/src/pages/substore.tsx",
    "chars": 3341,
    "preview": "import { Button } from '@heroui/react'\nimport BasePage from '@renderer/components/base/base-page'\nimport { useAppConfig "
  },
  {
    "path": "src/renderer/src/pages/sysproxy.tsx",
    "chars": 7003,
    "preview": "import { Button, Input, Tab, Tabs } from '@heroui/react'\nimport BasePage from '@renderer/components/base/base-page'\nimpo"
  },
  {
    "path": "src/renderer/src/pages/tun.tsx",
    "chars": 9422,
    "preview": "import { Button, Input, Switch, Tab, Tabs } from '@heroui/react'\nimport BasePage from '@renderer/components/base/base-pa"
  },
  {
    "path": "src/renderer/src/routes/index.tsx",
    "chars": 1565,
    "preview": "import { Navigate } from 'react-router-dom'\nimport Override from '@renderer/pages/override'\nimport Proxies from '@render"
  },
  {
    "path": "src/renderer/src/utils/calc.ts",
    "chars": 1194,
    "preview": "export function calcTraffic(byte: number): string {\n  if (byte < 1024) return `${formatNumString(byte)} B`\n  byte /= 102"
  },
  {
    "path": "src/renderer/src/utils/dayjs.ts",
    "chars": 481,
    "preview": "import dayjs from 'dayjs'\nimport 'dayjs/locale/zh-cn'\nimport 'dayjs/locale/en'\nimport relativeTime from 'dayjs/plugin/re"
  },
  {
    "path": "src/renderer/src/utils/debounce.ts",
    "chars": 402,
    "preview": "/* eslint-disable @typescript-eslint/no-explicit-any */\nexport default function debounce<T extends (...args: any[]) => v"
  }
]

// ... and 16 more files (download for full content)

About this extraction

This page contains the full source code of the mihomo-party-org/clash-party GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 216 files (3.4 MB), approximately 894.9k tokens, and a symbol index with 516 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!