Full Code of modstart-lib/focusany for AI

main 8a5b80119501 cached
368 files
1.5 MB
365.7k tokens
843 symbols
1 requests
Download .txt
Showing preview only (1,660K chars total). Download the full file or copy to clipboard to get everything.
Repository: modstart-lib/focusany
Branch: main
Commit: 8a5b80119501
Files: 368
Total size: 1.5 MB

Directory structure:
gitextract_d_g1kj1z/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── help_wanted.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       ├── build.yml
│       ├── main-build.yml
│       └── tag-release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── cli/
│   ├── cmd/
│   │   ├── plugin.go
│   │   ├── root.go
│   │   └── version.go
│   ├── go.mod
│   ├── go.sum
│   ├── internal/
│   │   ├── client.go
│   │   └── config.go
│   └── main.go
├── electron/
│   ├── config/
│   │   ├── common.ts
│   │   ├── contextMenu.ts
│   │   ├── icon.ts
│   │   ├── lang.ts
│   │   ├── menu.ts
│   │   ├── tray.ts
│   │   └── window.ts
│   ├── declarations/
│   │   ├── electron.d.ts
│   │   └── svg.d.ts
│   ├── electron-env.d.ts
│   ├── lib/
│   │   ├── api.ts
│   │   ├── devtools.ts
│   │   ├── env-main.ts
│   │   ├── env.ts
│   │   ├── hooks.ts
│   │   ├── permission.ts
│   │   ├── pinyin-util.ts
│   │   ├── process.ts
│   │   └── util.ts
│   ├── main/
│   │   ├── fastPanel.ts
│   │   └── index.ts
│   ├── mapi/
│   │   ├── app/
│   │   │   ├── icons.ts
│   │   │   ├── index.ts
│   │   │   ├── lib/
│   │   │   │   └── position.ts
│   │   │   ├── loading.ts
│   │   │   ├── main.ts
│   │   │   ├── render.ts
│   │   │   ├── setup.ts
│   │   │   └── toast.ts
│   │   ├── config/
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── db/
│   │   │   ├── db.ts
│   │   │   ├── main.ts
│   │   │   ├── migration.ts
│   │   │   ├── render.ts
│   │   │   └── type.d.ts
│   │   ├── env.ts
│   │   ├── event/
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── file/
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── httpserver/
│   │   │   └── main.ts
│   │   ├── keys/
│   │   │   ├── main.ts
│   │   │   └── type.ts
│   │   ├── kvdb/
│   │   │   ├── kvdb.ts
│   │   │   ├── main.ts
│   │   │   ├── render.ts
│   │   │   ├── types.ts
│   │   │   ├── version.ts
│   │   │   └── webdav.ts
│   │   ├── log/
│   │   │   ├── beacon-render.ts
│   │   │   ├── beacon.ts
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── main.ts
│   │   ├── manager/
│   │   │   ├── automation/
│   │   │   │   └── index.ts
│   │   │   ├── backend/
│   │   │   │   └── index.ts
│   │   │   ├── clipboard/
│   │   │   │   ├── clipboardFiles.ts
│   │   │   │   └── index.ts
│   │   │   ├── code/
│   │   │   │   └── index.ts
│   │   │   ├── config/
│   │   │   │   └── config.ts
│   │   │   ├── editor/
│   │   │   │   └── index.ts
│   │   │   ├── hotkey/
│   │   │   │   ├── handle.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── simulate.ts
│   │   │   ├── lib/
│   │   │   │   ├── cache.ts
│   │   │   │   └── hooks.ts
│   │   │   ├── main.ts
│   │   │   ├── manager.ts
│   │   │   ├── plugin/
│   │   │   │   ├── colorPicker.ts
│   │   │   │   ├── event.ts
│   │   │   │   ├── http.ts
│   │   │   │   ├── httpMCP.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── llm.ts
│   │   │   │   ├── log.ts
│   │   │   │   ├── permission.ts
│   │   │   │   ├── screenCapture.ts
│   │   │   │   ├── screenRecord.ts
│   │   │   │   └── sdk.ts
│   │   │   ├── render.ts
│   │   │   ├── storage/
│   │   │   │   └── index.ts
│   │   │   ├── system/
│   │   │   │   ├── asset/
│   │   │   │   │   └── icon.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── plugin/
│   │   │   │       ├── app/
│   │   │   │       │   ├── linux/
│   │   │   │       │   │   ├── icon.ts
│   │   │   │       │   │   ├── index.ts
│   │   │   │       │   │   └── title.ts
│   │   │   │       │   ├── mac/
│   │   │   │       │   │   ├── icon.ts
│   │   │   │       │   │   ├── index.ts
│   │   │   │       │   │   └── title.ts
│   │   │   │       │   ├── type.ts
│   │   │   │       │   ├── util/
│   │   │   │       │   │   └── index.ts
│   │   │   │       │   └── win/
│   │   │   │       │       ├── icon.ts
│   │   │   │       │       ├── index.ts
│   │   │   │       │       └── title.ts
│   │   │   │       ├── app.ts
│   │   │   │       ├── file.ts
│   │   │   │       ├── store/
│   │   │   │       │   ├── action.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── store.ts
│   │   │   │       ├── system/
│   │   │   │       │   └── action.ts
│   │   │   │       └── system.ts
│   │   │   ├── type.ts
│   │   │   └── window/
│   │   │       ├── index.ts
│   │   │       └── remoteWeb.ts
│   │   ├── misc/
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── protocol/
│   │   │   └── main.ts
│   │   ├── render.ts
│   │   ├── statistics/
│   │   │   └── render.ts
│   │   ├── storage/
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── ui/
│   │   │   ├── index.ts
│   │   │   └── render.ts
│   │   ├── updater/
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── user/
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   └── util.ts
│   ├── page/
│   │   ├── about.ts
│   │   ├── feedback.ts
│   │   ├── guide.ts
│   │   ├── index.ts
│   │   ├── log.ts
│   │   ├── monitor.ts
│   │   ├── payment.ts
│   │   ├── setup.ts
│   │   └── user.ts
│   ├── preload/
│   │   ├── focusany.ts
│   │   ├── index.ts
│   │   └── plugin.ts
│   └── resources/
│       └── build/
│           ├── entitlements.mac.plist
│           ├── logo.icns
│           └── logo_1024x1024.psd
├── electron-builder.json5
├── entitlements.mac.plist
├── index.html
├── package.json
├── page/
│   ├── about.html
│   ├── detachWindow.html
│   ├── fastPanel.html
│   ├── feedback.html
│   ├── guide.html
│   ├── log.html
│   ├── monitor.html
│   ├── payment.html
│   ├── setup.html
│   ├── store.html
│   ├── system.html
│   ├── user.html
│   └── workflow.html
├── postcss.config.js
├── public/
│   ├── iconfont/
│   │   ├── iconfont.css
│   │   ├── iconfont.js
│   │   └── iconfont.json
│   └── static/
│       └── pluginEmpty.html
├── scripts/
│   ├── build_optimize.cjs
│   ├── common.cjs
│   ├── icon_convert.sh
│   ├── init.sh
│   └── notarize.cjs
├── sdk/
│   ├── .babelrc
│   ├── .github/
│   │   └── workflows/
│   │       └── tag-release.yml
│   ├── .gitignore
│   ├── .npmignore
│   ├── .nvmrc
│   ├── README.md
│   ├── bin/
│   │   └── command.ts
│   ├── config.schema.json
│   ├── electron-browser-window.d.ts
│   ├── electron.d.ts
│   ├── focusany-shim.d.ts
│   ├── focusany-shim.ts
│   ├── focusany.d.ts
│   ├── index.d.ts
│   ├── index.ts
│   ├── package.json
│   ├── shim.html
│   ├── tests/
│   │   └── config.json
│   └── tsconfig.json
├── src/
│   ├── App.vue
│   ├── api/
│   │   ├── types/
│   │   │   └── base.ts
│   │   └── user.ts
│   ├── app/
│   │   ├── dragWindow.ts
│   │   └── locale.ts
│   ├── components/
│   │   ├── AppQuitConfirm.vue
│   │   ├── PageNav.vue
│   │   ├── Setting/
│   │   │   ├── SettingAbout.vue
│   │   │   ├── SettingBasic.vue
│   │   │   ├── SettingEnv.vue
│   │   │   └── components/
│   │   │       └── SettingEnvHubRoot.vue
│   │   ├── TextTruncateView.vue
│   │   └── common/
│   │       ├── AudioPlayer.vue
│   │       ├── CodeViewer.vue
│   │       ├── CodeViewerDialog.vue
│   │       ├── DataConfigDialogButton.vue
│   │       ├── DragPasteContainer.vue
│   │       ├── FeedbackTicketButton.vue
│   │       ├── FileExt.vue
│   │       ├── FileLogViewer.vue
│   │       ├── FilesSelector.vue
│   │       ├── HtmlViewer.vue
│   │       ├── InputInlineEditor.vue
│   │       ├── LogViewer.vue
│   │       ├── LogViewerDialog.vue
│   │       ├── MEmpty.vue
│   │       ├── MLoading.vue
│   │       ├── PageWebviewStatus.vue
│   │       ├── ProUpgrade.vue
│   │       ├── SettingItemYesNo.vue
│   │       ├── SettingItemYesNoDefault.vue
│   │       ├── TaskBizStatus.vue
│   │       ├── UpdaterButton.vue
│   │       ├── VideoPlayer.vue
│   │       ├── WebFileSelectButton.vue
│   │       ├── dataConfig.ts
│   │       ├── index.ts
│   │       └── util.ts
│   ├── config.ts
│   ├── declarations/
│   │   ├── svg.d.ts
│   │   └── type.d.ts
│   ├── entry/
│   │   ├── Page.vue
│   │   ├── about.ts
│   │   ├── detachWindow.ts
│   │   ├── fastPanel.ts
│   │   ├── feedback.ts
│   │   ├── guide.ts
│   │   ├── log.ts
│   │   ├── monitor.ts
│   │   ├── payment.ts
│   │   ├── setup.ts
│   │   ├── store.ts
│   │   ├── system.ts
│   │   ├── user.ts
│   │   └── workflow.ts
│   ├── hooks/
│   │   └── user.ts
│   ├── lang/
│   │   ├── en-US.json
│   │   ├── index.ts
│   │   └── zh-CN.json
│   ├── layouts/
│   │   ├── Main.vue
│   │   └── Raw.vue
│   ├── lib/
│   │   ├── api.ts
│   │   ├── audio.ts
│   │   ├── components/
│   │   │   └── Prompt.vue
│   │   ├── dialog.ts
│   │   ├── env.ts
│   │   ├── error.ts
│   │   ├── event.ts
│   │   ├── file.ts
│   │   ├── markdown.ts
│   │   ├── storage.ts
│   │   ├── toggle.ts
│   │   ├── ui.ts
│   │   └── util.ts
│   ├── main.ts
│   ├── module/
│   │   └── Model/
│   │       ├── ModelGenerateButton.vue
│   │       ├── ModelGenerator.vue
│   │       ├── ModelPromptDataConfigButton.vue
│   │       ├── ModelSelector.vue
│   │       ├── ModelSetting.vue
│   │       ├── ModelSettingDialog.vue
│   │       ├── components/
│   │       │   ├── ModelAddDialog.vue
│   │       │   ├── ModelEditDialog.vue
│   │       │   ├── ProviderAddDialog.vue
│   │       │   ├── ProviderEditDialog.vue
│   │       │   └── ProviderTestDialog.vue
│   │       ├── models.ts
│   │       ├── provider/
│   │       │   ├── driver/
│   │       │   │   ├── base.ts
│   │       │   │   └── openai.ts
│   │       │   └── provider.ts
│   │       ├── providers.ts
│   │       ├── store/
│   │       │   └── model.ts
│   │       └── types.ts
│   ├── pages/
│   │   ├── DetachWindow/
│   │   │   └── operate.ts
│   │   ├── FastPanel/
│   │   │   ├── FastPanelResult.vue
│   │   │   ├── FastPanelSearch.vue
│   │   │   └── Lib/
│   │   │       └── resultOperate.ts
│   │   ├── Home.vue
│   │   ├── Main/
│   │   │   ├── Components/
│   │   │   │   ├── ResultActionCodeError.vue
│   │   │   │   ├── ResultActionCodeItemList.vue
│   │   │   │   ├── ResultActionCodeLoading.vue
│   │   │   │   ├── ResultItem.vue
│   │   │   │   ├── ResultLoading.vue
│   │   │   │   └── ResultWindowItem.vue
│   │   │   ├── Lib/
│   │   │   │   ├── entryListener.ts
│   │   │   │   ├── mainOperate.ts
│   │   │   │   ├── resultOperate.ts
│   │   │   │   ├── resultResize.ts
│   │   │   │   ├── searchOperate.ts
│   │   │   │   └── viewOperate.ts
│   │   │   ├── MainResult.vue
│   │   │   └── MainSearch.vue
│   │   ├── PageAbout.vue
│   │   ├── PageDetachWindow.vue
│   │   ├── PageFastPanel.vue
│   │   ├── PageFeedback.vue
│   │   ├── PageGuide.vue
│   │   ├── PageLog.vue
│   │   ├── PageMonitor.vue
│   │   ├── PagePayment.vue
│   │   ├── PageSetup.vue
│   │   ├── PageStore.vue
│   │   ├── PageSystem.vue
│   │   ├── PageUser.vue
│   │   ├── PageWorkflow.vue
│   │   ├── Setting.vue
│   │   └── System/
│   │       ├── SystemAbout.vue
│   │       ├── SystemAction.vue
│   │       ├── SystemData.vue
│   │       ├── SystemFile.vue
│   │       ├── SystemLaunch.vue
│   │       ├── SystemMCP.vue
│   │       ├── SystemModel.vue
│   │       ├── SystemPlugin.vue
│   │       ├── SystemSetting.vue
│   │       ├── SystemUser.vue
│   │       └── components/
│   │           ├── ActionTypeIcon.vue
│   │           ├── HotkeyInput.vue
│   │           ├── SystemActionMatchDetailDialog.vue
│   │           ├── SystemDataBackup/
│   │           │   ├── WebDavManage.vue
│   │           │   └── WebDavManageSettingDialog.vue
│   │           ├── SystemDataBackupDialog.vue
│   │           ├── SystemDataViewDetailDialog.vue
│   │           ├── SystemDataViewDialog.vue
│   │           └── type.ts
│   ├── router.ts
│   ├── store/
│   │   ├── index.ts
│   │   └── modules/
│   │       ├── app.ts
│   │       ├── manager.ts
│   │       ├── setting.ts
│   │       ├── task.ts
│   │       └── user.ts
│   ├── style.less
│   ├── task/
│   │   └── index.ts
│   ├── types/
│   │   └── Manager.ts
│   └── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.flat.txt
└── vite.config.ts

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

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

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

[*.md]
trim_trailing_whitespace = false

[*.{yml, yaml}]
indent_size = 4

[*.{less, css}]
indent_size = 4




================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---

name: 🐞 Bug report
about: Create a report to help us improve
title: "[Bug] the title of bug report"
labels: bug
assignees: ''

---

#### Describe the bug


================================================
FILE: .github/ISSUE_TEMPLATE/help_wanted.md
================================================
---
name: 🥺 Help wanted
about: Confuse about the use of electron-vue-vite
title: "[Help] the title of help wanted report"
labels: help wanted
assignees: ''

---

#### Describe the problem you confuse


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!-- Thank you for contributing! -->

### Description

<!-- Please insert your description here and provide especially info about the "what" this PR is solving -->

### What is the purpose of this pull request? <!-- (put an "X" next to an item) -->

- [ ] Bug fix
- [ ] New Feature
- [ ] Documentation update
- [ ] Other


================================================
FILE: .github/workflows/build.yml
================================================
name: Build

on:
  push:
    tags:
      - v*.*.*
    branches:
      - main
  workflow_dispatch:

jobs:
  build:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
        include:
          - os: ubuntu-latest
            arch: [ arm64, amd64 ]
          - os: macos-latest
            arch: [ arm64, amd64 ]
          - os: windows-latest
            arch: [ arm64, amd64 ]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Git clone repo
        if: runner.os == 'Linux' || runner.os == 'macOS'
        env:
          GIT_USER: ${{ secrets.GIT_USER }}
          GIT_PASS: ${{ secrets.GIT_PASS }}
          GIT_REPO_BASE: ${{ secrets.GIT_REPO_BASE }}
          GIT_HOST: ${{ secrets.GIT_HOST }}
        run: |
          git clone -b main "https://${GIT_USER}:${GIT_PASS}@${GIT_HOST}/${GIT_REPO_BASE}/focusany-pro.git" code

      - name: Git clone repo
        if: runner.os == 'Windows'
        env:
          GIT_USER: ${{ secrets.GIT_USER }}
          GIT_PASS: ${{ secrets.GIT_PASS }}
          GIT_REPO_BASE: ${{ secrets.GIT_REPO_BASE }}
          GIT_HOST: ${{ secrets.GIT_HOST }}
        shell: pwsh
        run: |
          git clone -b main "https://${env:GIT_USER}:${env:GIT_PASS}@${env:GIT_HOST}/${env:GIT_REPO_BASE}/focusany-pro.git" code

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Build Prepare (Linux)
        if: runner.os == 'Linux'
        run: |
          sudo apt-get update
          sudo apt-get install -y build-essential pkg-config

      - name: Build Prepare (macOS)
        if: runner.os == 'macOS'
        run: |
          brew install python-setuptools

      - name: Cert Prepare (macOS)
        if: runner.os == 'macOS'
        env:
          MACOS_CERTIFICATE: ${{ secrets.CORP_MACOS_CERTIFICATE }}
          MACOS_CERTIFICATE_PASSWORD: ${{ secrets.CORP_MACOS_CERTIFICATE_PASSWORD }}
        run: |
          echo "find-identity"
          security find-identity -p codesigning
          echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
          security create-keychain -p "" build.keychain
          security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
          security list-keychains -s build.keychain
          security set-keychain-settings -t 3600 -u build.keychain
          security unlock-keychain -p "" build.keychain
          echo "find-identity"
          security find-identity -v -p codesigning build.keychain
          echo "find-identity"
          security find-identity -p codesigning
          echo "set-key-partition-list"
          security set-key-partition-list -S apple-tool:,apple: -s -k "" -l "Mac Developer ID Application: Xi'an Yanyi Information Technology Co., Ltd" -t private build.keychain
          echo "export"
          security export -k build.keychain -t certs -f x509 -p -o certificate.cer
          echo "add-trusted-cert"
          sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain certificate.cer
          echo "find-identity"
          security find-identity -p codesigning

      - name: Install Dependencies (Linux/macOS)
        if: runner.os == 'Linux' || runner.os == 'macOS'
        working-directory: code
        run: npm install

      - name: Install Dependencies (Windows)
        if: runner.os == 'Windows'
        working-directory: code
        shell: pwsh
        run: npm install

      - name: init ( Windows )
        if: runner.os == 'Windows'
        working-directory: code
        shell: pwsh
        run: bash ./scripts/init.sh

      - name: init ( Linux/osx )
        if: runner.os == 'Linux' || runner.os == 'macOS'
        working-directory: code
        run: bash ./scripts/init.sh

      - name: Build Release Files
        working-directory: code
        run: |
            npm run build
        env:
          DEBUG: "electron-notarize:*"
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

      - name: Set Build Name ( Linux / macOS )
        if: runner.os == 'Linux' || runner.os == 'macOS'
        run: |
          DIST_FILE_NAME=${{ runner.os }}-${{ runner.arch }}-v$(date +%Y%m%d_%H%M%S)-${RANDOM}
          echo ::add-mask::$DIST_FILE_NAME
          echo DIST_FILE_NAME=$DIST_FILE_NAME >> $GITHUB_ENV

      - name: Set Build Name ( Windows )
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
          $randomNumber = Get-Random -Minimum 10000 -Maximum 99999
          $DIST_FILE_NAME = "Windows-X64-v$(Get-Date -Format 'yyyyMMdd_HHmmss')-$randomNumber"
          Write-Host "::add-mask::$DIST_FILE_NAME"
          echo "DIST_FILE_NAME=$DIST_FILE_NAME" >> $env:GITHUB_ENV


      - name: Rename output files (Linux/macOS)
        if: runner.os == 'Linux' || runner.os == 'macOS'
        run: |
            find code/dist-release/ -type f -name 'FocusAnyPro-*' -exec bash -c 'f="{}"; mv "$f" "${f/FocusAnyPro-/FocusAny-}"' \;

      - name: Upload Sourcemaps
        if: runner.os == 'Linux'
        working-directory: code
        env:
          GROW_URL: ${{ secrets.GROW_URL }}
          GROW_APP_NAME: ${{ secrets.GROW_APP_NAME }}
          GROW_ADMIN_API_TOKEN: ${{ secrets.GROW_ADMIN_API_TOKEN }}
        run: |
          BUILD_ID=$(node -p "require('./dist/build.json').buildId")
          echo "Uploading sourcemaps for buildId=${BUILD_ID} ..."
          find dist dist-electron/main -name "*.map" | while read f; do
            echo "  uploading $f"
            RESP=$(curl -s -X POST "${GROW_URL}/api/admin/grow/buildUpload" \
              -H "Authorization: Bearer ${GROW_ADMIN_API_TOKEN}" \
              -F "appName=${GROW_APP_NAME}" \
              -F "buildId=${BUILD_ID}" \
              -F "file=@${f}" \
              -F "name=${f}")
            echo "  response: ${RESP}"
          done
          echo "Sourcemap upload done."

      - name: Rename output files (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
            Get-ChildItem code\dist-release\FocusAnyPro-* | Rename-Item -NewName { $_.Name -replace 'FocusAnyPro-','FocusAny-' }


      - name: Upload
        if: github.event_name == 'workflow_dispatch'
        uses: modstart/github-oss-action@master
        with:
          title: ${{ github.event.head_commit.message }}
          key-id: ${{ secrets.OSS_2_KEY_ID }}
          key-secret: ${{ secrets.OSS_2_KEY_SECRET }}
          region: ${{ secrets.OSS_2_REGION }}
          bucket: ${{ secrets.OSS_2_BUCKET }}
          callbackTitle: ✅FocusAny-${{ runner.os }}-打包成功
          callback: ${{ secrets.OSS_2_CALLBACK }}
          assets: |
            code/dist-release/*.exe:apps/focusany-${{ env.DIST_FILE_NAME }}/
            code/dist-release/*.dmg:apps/focusany-${{ env.DIST_FILE_NAME }}/
            code/dist-release/*.AppImage:apps/focusany-${{ env.DIST_FILE_NAME }}/
            code/dist-release/*.deb:apps/focusany-${{ env.DIST_FILE_NAME }}/

      - name: Release Assets
        if: github.event_name == 'push'
        uses: softprops/action-gh-release@v2
        with:
          draft: true
          prerelease: false
          fail_on_unmatched_files: false
          overwrite_files: true
          files: |
            code/dist-release/*.exe
            code/dist-release/*.dmg
            code/dist-release/*.AppImage
            code/dist-release/*.deb
        env:
          GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}



================================================
FILE: .github/workflows/main-build.yml
================================================
name: MainBuild

on:
    push:
        branches:
            - mainx

jobs:
  build:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
          include:
              - os: ubuntu-latest
                arch: [arm64, amd64]
              - os: macos-latest
                arch: [arm64, amd64]
              - os: windows-latest
                arch: [arm64, amd64]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Build Prepare (Linux)
        if: runner.os == 'Linux'
        run: |
            sudo apt-get update
            sudo apt-get install -y build-essential pkg-config

      - name: Build Prepare (macOS)
        if: runner.os == 'macOS'
        run: |
            brew install python-setuptools

      - name: Cert Prepare (macOS)
        if: runner.os == 'macOS'
        env:
            MACOS_CERTIFICATE: ${{ secrets.CORP_MACOS_CERTIFICATE }}
            MACOS_CERTIFICATE_PASSWORD: ${{ secrets.CORP_MACOS_CERTIFICATE_PASSWORD }}
        run: |
            echo "find-identity"
            security find-identity -p codesigning
            echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
            security create-keychain -p "" build.keychain
            security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
            security list-keychains -s build.keychain
            security set-keychain-settings -t 3600 -u build.keychain
            security unlock-keychain -p "" build.keychain
            echo "find-identity"
            security find-identity -v -p codesigning build.keychain
            echo "find-identity"
            security find-identity -p codesigning
            echo "set-key-partition-list"
            security set-key-partition-list -S apple-tool:,apple: -s -k "" -l "Mac Developer ID Application: Xi'an Yanyi Information Technology Co., Ltd" -t private build.keychain
            echo "export"
            security export -k build.keychain -t certs -f x509 -p -o certificate.cer
            echo "add-trusted-cert"
            sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain certificate.cer
            echo "find-identity"
            security find-identity -p codesigning

      - name: Install Dependencies (Linux/macOS)
        if: runner.os == 'Linux' || runner.os == 'macOS'
        run: |
            npm install --ignore-scripts
            rm -rf node_modules/canvas
            npx electron-builder install-app-deps

      - name: Install Dependencies (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
            npm install --ignore-scripts
            if (Test-Path node_modules\canvas) { Remove-Item -Recurse -Force node_modules\canvas }
            npx electron-builder install-app-deps

      - name: init ( Windows )
        if: runner.os == 'Windows'
        shell: pwsh
        run: bash ./scripts/init.sh

      - name: init ( Linux/osx )
        if: runner.os == 'Linux' || runner.os == 'macOS'
        run: bash ./scripts/init.sh

      - name: Build Release Files
        run: npm run build
        env:
          DEBUG: "electron-notarize:*"
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

      - name: Set Build Name ( Linux / macOS )
        if: runner.os == 'Linux' || runner.os == 'macOS'
        run: |
            DIST_FILE_NAME=${{ runner.os }}-${{ runner.arch }}-v$(date +%Y%m%d_%H%M%S)-${RANDOM}
            echo ::add-mask::$DIST_FILE_NAME
            echo DIST_FILE_NAME=$DIST_FILE_NAME >> $GITHUB_ENV

      - name: Set Build Name ( Windows )
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
            $randomNumber = Get-Random -Minimum 10000 -Maximum 99999
            $DIST_FILE_NAME = "Windows-X64-v$(Get-Date -Format 'yyyyMMdd_HHmmss')-$randomNumber"
            Write-Host "::add-mask::$DIST_FILE_NAME"
            echo "DIST_FILE_NAME=$DIST_FILE_NAME" >> $env:GITHUB_ENV

      - name: Upload
        uses: modstart/github-oss-action@master
        with:
            title: ${{ github.event.head_commit.message }}
            key-id: ${{ secrets.OSS_2_KEY_ID }}
            key-secret: ${{ secrets.OSS_2_KEY_SECRET }}
            region: ${{ secrets.OSS_2_REGION }}
            bucket: ${{ secrets.OSS_2_BUCKET }}
            callbackUrlSign: ${{ secrets.OSS_2_CALLBACK_URL_SIGN }}
            callback: ${{ secrets.OSS_2_CALLBACK }}
            assets: |
                dist-release/*.exe:apps/focusany-${{ env.DIST_FILE_NAME }}/
                dist-release/*.dmg:apps/focusany-${{ env.DIST_FILE_NAME }}/
                dist-release/*.AppImage:apps/focusany-${{ env.DIST_FILE_NAME }}/
                dist-release/*.deb:apps/focusany-${{ env.DIST_FILE_NAME }}/

      - name: Upload Artifact Windows
        if: runner.os == 'Windows'
        uses: actions/upload-artifact@v4
        with:
            name: windows-artifact
            path: |
                dist-release/*.exe

      - name: Upload Artifact Macos
        if: runner.os == 'macOS'
        uses: actions/upload-artifact@v4
        with:
            name: macos-artifact
            path: |
                dist-release/*.dmg

      - name: Upload Artifact Linux
        if: runner.os == 'Linux'
        uses: actions/upload-artifact@v4
        with:
            name: linux-artifact
            path: |
                dist-release/*.AppImage
                dist-release/*.deb



================================================
FILE: .github/workflows/tag-release.yml
================================================
name: TagRelease

on:
    push:
        tags:
            - v*.*.*

jobs:
  build:
    runs-on: ${{ matrix.os }}

    strategy:
      matrix:
          include:
              - os: ubuntu-latest
                arch: [arm64, amd64]
              - os: macos-latest
                arch: [arm64, amd64]
              - os: windows-latest
                arch: [arm64, amd64]

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20

      - name: Build Prepare (Linux)
        if: runner.os == 'Linux'
        run: |
            sudo apt-get update
            sudo apt-get install -y build-essential pkg-config

      - name: Build Prepare (macOS)
        if: runner.os == 'macOS'
        run: |
            brew install python-setuptools

      - name: Cert Prepare (macOS)
        if: runner.os == 'macOS'
        env:
            MACOS_CERTIFICATE: ${{ secrets.CORP_MACOS_CERTIFICATE }}
            MACOS_CERTIFICATE_PASSWORD: ${{ secrets.CORP_MACOS_CERTIFICATE_PASSWORD }}
        run: |
            echo "find-identity"
            security find-identity -p codesigning
            echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
            security create-keychain -p "" build.keychain
            security import certificate.p12 -k build.keychain -P "$MACOS_CERTIFICATE_PASSWORD" -T /usr/bin/codesign
            security list-keychains -s build.keychain
            security set-keychain-settings -t 3600 -u build.keychain
            security unlock-keychain -p "" build.keychain
            echo "find-identity"
            security find-identity -v -p codesigning build.keychain
            echo "find-identity"
            security find-identity -p codesigning
            echo "set-key-partition-list"
            security set-key-partition-list -S apple-tool:,apple: -s -k "" -l "Mac Developer ID Application: Xi'an Yanyi Information Technology Co., Ltd" -t private build.keychain
            echo "export"
            security export -k build.keychain -t certs -f x509 -p -o certificate.cer
            echo "add-trusted-cert"
            sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain certificate.cer
            echo "find-identity"
            security find-identity -p codesigning

      - name: Install Dependencies (Linux/macOS)
        if: runner.os == 'Linux' || runner.os == 'macOS'
        run: |
            npm install --ignore-scripts
            rm -rf node_modules/canvas
            npx electron-builder install-app-deps

      - name: Install Dependencies (Windows)
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
            npm install --ignore-scripts
            if (Test-Path node_modules\canvas) { Remove-Item -Recurse -Force node_modules\canvas }
            npx electron-builder install-app-deps

      - name: init ( Windows )
        if: runner.os == 'Windows'
        shell: pwsh
        run: bash ./scripts/init.sh

      - name: init ( Linux/osx )
        if: runner.os == 'Linux' || runner.os == 'macOS'
        run: bash ./scripts/init.sh

      - name: Build Release Files
        run: npm run build
        env:
          DEBUG: "electron-notarize:*"
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          APPLE_ID: ${{ secrets.APPLE_ID }}
          APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
          APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

      - name: Set Build Name ( Linux / macOS )
        if: runner.os == 'Linux' || runner.os == 'macOS'
        run: |
            DIST_FILE_NAME=${{ runner.os }}-${{ runner.arch }}-v$(date +%Y%m%d_%H%M%S)-${RANDOM}
            echo ::add-mask::$DIST_FILE_NAME
            echo DIST_FILE_NAME=$DIST_FILE_NAME >> $GITHUB_ENV

      - name: Set Build Name ( Windows )
        if: runner.os == 'Windows'
        shell: pwsh
        run: |
            $randomNumber = Get-Random -Minimum 10000 -Maximum 99999
            $DIST_FILE_NAME = "Windows-X64-v$(Get-Date -Format 'yyyyMMdd_HHmmss')-$randomNumber"
            Write-Host "::add-mask::$DIST_FILE_NAME"
            echo "DIST_FILE_NAME=$DIST_FILE_NAME" >> $env:GITHUB_ENV



================================================
FILE: .gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
/dist
/dist-ssr
/dist-electron
/dist-release
*.local

# Editor directories and files
.vscode/.debug.env
.idea/
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# lockfile
pnpm-lock.yaml
yarn.lock
database.db

/focusany-plugin-*
/data
/data-*
.vscode
.github/copilot-instructions.md
.vscode
.github/copilot-instructions.md
.vscode


================================================
FILE: .npmrc
================================================
# For electron-builder
# https://github.com/electron-userland/electron-builder/issues/6289#issuecomment-1042620422
shamefully-hoist=true

# For China 🇨🇳 developers
electron_mirror=https://npmmirror.com/mirrors/electron/
electron_builder_binaries_mirror=https://npmmirror.com/mirrors/electron-builder-binaries/


================================================
FILE: .nvmrc
================================================
20


================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/

TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION

1. Definitions.

"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.

"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.

"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.

"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.

"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.

"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.

"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).

"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.

"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."

"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.

2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.

3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.

4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:

You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.

5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.

6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.

7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.

8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.

9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.

END OF TERMS AND CONDITIONS


================================================
FILE: Makefile
================================================


# make test              → 完整测试套件(构建验证)
# make test biz          → 直接运行全部 test/biz/*.test.ts(需已启动 Electron)
# make test ui           → 直接运行全部 test/ui/*.test.ts(需已启动 Electron)
# make test biz xxx      → 单独运行 test/biz/xxx.test.ts
# make test ui  xxx      → 单独运行 test/ui/xxx.test.ts
_TEST_TYPE := $(word 2,$(MAKECMDGOALS))
_TEST_NAME := $(word 3,$(MAKECMDGOALS))

.PHONY: test biz ui dev-seed dev publish build_and_install build-cli

test:
	@if [ "$(_TEST_TYPE)" = "biz" ] && [ -n "$(_TEST_NAME)" ]; then \
		npx tsx test/biz/$(_TEST_NAME).test.ts; \
	elif [ "$(_TEST_TYPE)" = "ui" ] && [ -n "$(_TEST_NAME)" ]; then \
		npx tsx test/ui/$(_TEST_NAME).test.ts; \
	elif [ "$(_TEST_TYPE)" = "biz" ]; then \
		failed=0; \
		for f in test/biz/*.test.ts; do [ -f "$$f" ] || continue; npx tsx "$$f" || failed=1; done; \
		exit $$failed; \
	elif [ "$(_TEST_TYPE)" = "ui" ]; then \
		failed=0; \
		for f in test/ui/*.test.ts; do [ -f "$$f" ] || continue; npx tsx "$$f" || failed=1; done; \
		exit $$failed; \
	else \
		npm run build:preview 2>&1 | tail -20; \
	fi

biz ui:
	@:

dev-seed:
	npx tsx test/dev-seed.ts

dev:
	npm run dev:mac

publish:
	ss-publish publish ../focusany
	cd ../focusany && make test

build_and_install:
	rm -rfv dist-release/*.dmg
	rm -rfv dist-release/*.blockmap
	rm -rfv dist-release/*.yml
	rm -rfv dist-release/*.yaml
	rm -rfv dist-release/*.zip
	npm run build:mac-arm
	rm -rfv /Applications/FocusAny.app
	cp -a ./dist-release/mac-arm64/FocusAny.app /Applications

build-cli:
	@VERSION=$$(node -p "require('./package.json').version") && \
	mkdir -p dist-cli && \
	echo "Building CLI version $$VERSION ..." && \
	cd cli && \
	GOOS=darwin  GOARCH=amd64  go build -ldflags="-X main.Version=$$VERSION" -o ../dist-cli/focusany-darwin-x64    . && \
	GOOS=darwin  GOARCH=arm64  go build -ldflags="-X main.Version=$$VERSION" -o ../dist-cli/focusany-darwin-arm64  . && \
	GOOS=linux   GOARCH=amd64  go build -ldflags="-X main.Version=$$VERSION" -o ../dist-cli/focusany-linux-x64     . && \
	GOOS=linux   GOARCH=arm64  go build -ldflags="-X main.Version=$$VERSION" -o ../dist-cli/focusany-linux-arm64   . && \
	GOOS=windows GOARCH=amd64  go build -ldflags="-X main.Version=$$VERSION" -o ../dist-cli/focusany-win-x64.exe   . && \
	GOOS=windows GOARCH=arm64  go build -ldflags="-X main.Version=$$VERSION" -o ../dist-cli/focusany-win-arm64.exe . && \
	echo "CLI binaries built in dist-cli/"

# 吸收 `make test biz/ui <name>` 中任意测试名称,防止 "No rule to make target" 报错
.DEFAULT:
	@:


================================================
FILE: README.md
================================================
# 🎯 FocusAny - 智能AI办公助理

<div align="center">
  <img src="./screenshots/cn/home.png" alt="FocusAny 主界面" width="800"/>
</div>

<div align="center">
  <img src="https://img.shields.io/badge/Framework-TS%2BVue3%2BElectron-blue" alt="Framework"/>
  <a href="https://focusany.com"><img src="https://img.shields.io/badge/WEB-focusany.com-blue" alt="Website"/></a>
  <a href="https://github.com/modstart-lib/focusany"><img src="https://img.shields.io/github/stars/modstart-lib/focusany.svg" alt="GitHub Stars"/></a>
  <a href="https://gitee.com/modstart-lib/focusany"><img src="https://gitee.com/modstart-lib/focusany/badge/star.svg" alt="Gitee Stars"/></a>
  <a href="https://gitcode.com/modstart-lib/focusany"><img src="https://gitcode.com/modstart-lib/focusany/star/badge.svg" alt="GitCode Stars"/></a>
  <img src="https://img.shields.io/badge/License-Apache%202.0-green" alt="License"/>
</div>

## ✨ 项目简介

`FocusAny` 是一个强大的智能AI办公助理,专为提升工作效率而设计。它支持市场插件和本地插件的一键启动,让你能够快速扩展功能,打造个性化的办公环境。

🚀 **快速启动** | 🔧 **插件扩展** | 🎨 **现代化界面** | 🌙 **暗黑模式支持**

## 📋 功能特性

- ⚙️ **功能设置**:自定义呼出快捷键,开机自启动
- 🛠️ **插件管理**:一键安装、卸载、启用/禁用插件
- 🎯 **动作管理**:内置和插件动作快速预览和管理
- 📁 **文件快速启动**:瞬间定位目标文件
- ⌨️ **快捷键启动**:全局快捷键快速启动应用
- 💾 **数据中心**:文件导出同步、WebDAV 文件同步
- 🌙 **暗黑模式**:护眼的暗黑主题界面

## 🔌 插件生态

FocusAny 拥有丰富的插件生态系统,支持各种办公场景:

### 插件市场概览

<table width="100%">
  <thead>
    <tr>
      <th colspan="2">🎪 插件市场</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td colspan="2">
        <img src="./screenshots/cn/plugin/Store.png" alt="插件市场" style="width:100%; border-radius: 8px;"/>
      </td>
    </tr>
    <tr>
      <th width="50%">📝 Markdown插件</th>
      <th>🛠️ Ctool程序员工具箱</th>
    </tr>
    <tr>
      <td><img src="./screenshots/cn/plugin/Markdown.png" alt="Markdown插件" style="width:100%; border-radius: 8px;"/></td>
      <td><img src="./screenshots/cn/plugin/Ctool.png" alt="Ctool工具箱" style="width:100%; border-radius: 8px;"/></td>
    </tr>
    <tr>
      <th>🌐 翻译插件</th>
      <th>📋 剪切板插件</th>
    </tr>
    <tr>
      <td><img src="./screenshots/cn/plugin/Translate.png" alt="翻译插件" style="width:100%; border-radius: 8px;"/></td>
      <td><img src="./screenshots/cn/plugin/Clipboard.png" alt="剪切板插件" style="width:100%; border-radius: 8px;"/></td>
    </tr>
    <tr>
      <th>🧠 脑图编辑器</th>
      <th>📊 mxGraph编辑器</th>
    </tr>
    <tr>
      <td><img src="./screenshots/cn/plugin/KityminderEditor.png" alt="脑图编辑器" style="width:100%; border-radius: 8px;"/></td>
      <td><img src="./screenshots/cn/plugin/MxgraphEditor.png" alt="mxGraph编辑器" style="width:100%; border-radius: 8px;"/></td>
    </tr>
    <tr>
      <th>🎨 tldraw白板</th>
      <th>✏️ Excalidraw白板</th>
    </tr>
    <tr>
      <td><img src="https://ms-assets.modstart.com/data/image/2024/12/27/20345_in2n_2839.png" alt="tldraw白板" style="width:100%; border-radius: 8px;"/></td>
      <td><img src="https://ms-assets.modstart.com/data/image/2024/12/23/27895_hlat_8257.png" alt="Excalidraw白板" style="width:100%; border-radius: 8px;"/></td>
    </tr>
    <tr>
      <th>🔐 密码管理器</th>
      <th>🖼️ 图片美化</th>
    </tr>
    <tr>
      <td><img src="https://ms-assets.modstart.com/data/image/2024/12/22/12047_w27p_4263.png" alt="密码管理器" style="width:100%; border-radius: 8px;"/></td>
      <td><img src="https://ms-assets.modstart.com/data/image/2024/12/22/53485_fk4f_3417.png" alt="图片美化" style="width:100%; border-radius: 8px;"/></td>
    </tr>
    <tr>
      <th>🔢 OTP两步验证</th>
      <th>📸 截图与贴图</th>
    </tr>
    <tr>
      <td><img src="https://ms-assets.modstart.com/data/image/2024/12/24/7709_81pr_6266.png" alt="OTP验证" style="width:100%; border-radius: 8px;"/></td>
      <td><img src="https://ms-assets.modstart.com/data/image/2024/12/22/42330_u3my_6770.png" alt="截图工具" style="width:100%; border-radius: 8px;"/></td>
    </tr>
  </tbody>
</table>

💡 **持续扩展**:FocusAny 正在不断添加更多插件,让你通过插件的方式实现无限可能的功能扩展!

## 🚀 快速开始

### 📦 安装使用

访问 [FocusAny 官网](https://focusany.com) 下载对应系统的安装包,一键安装即可开始使用!

### 🛠️ 本地开发

> ⚠️ 仅在 Node.js 20 环境下测试通过

#### 环境准备

**Ubuntu/Debian:**
```bash
sudo apt install -y make gcc g++ python3
```

**Windows:**
- 安装 Visual Studio 2019,并选择 "Desktop Development with C++" 组件

**macOS:**
- 安装 Python 3

#### 开发命令

```bash
# 安装项目依赖
npm install

# 启动开发模式
npm run dev

# 构建生产版本
npm run build
```

## 🏗️ 技术栈

<div align="center">
  <img src="https://img.shields.io/badge/Electron-47848F?style=for-the-badge&logo=electron&logoColor=white" alt="Electron"/>
  <img src="https://img.shields.io/badge/Vue.js-4FC08D?style=for-the-badge&logo=vue.js&logoColor=white" alt="Vue.js"/>
  <img src="https://img.shields.io/badge/TypeScript-3178C6?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript"/>
  <img src="https://img.shields.io/badge/Node.js-339933?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js"/>
</div>

## 📚 目录结构

```
focusany/
├── electron/          # Electron 主进程代码
├── src/              # Vue.js 前端源码
├── public/           # 静态资源
├── scripts/          # 构建脚本
├── screenshots/      # 截图资源
└── dist-release/     # 构建输出
```

## 🤝 社区交流

> 添加好友请备注 "FocusAny"

<table width="100%">
  <thead>
    <tr>
      <th width="50%">💬 微信交流群</th>
      <th>🗣️ QQ交流群</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><img src="https://modstart.com/code_dynamic/modstart_wx" alt="微信群" style="width:100%; border-radius: 8px;"/></td>
      <td><img src="https://modstart.com/code_dynamic/modstart_qq" alt="QQ群" style="width:100%; border-radius: 8px;"/></td>
    </tr>
  </tbody>
</table>

## 📄 许可证

本项目采用 [Apache-2.0](LICENSE) 许可证开源。

---

<div align="center">
  <p>⭐ 如果这个项目对你有帮助,请给我们一个 Star!</p>
  <p>💝 感谢所有贡献者和用户的支持</p>
</div>


================================================
FILE: SECURITY.md
================================================
# Security Policy

## Supported Versions

We strongly recommend always using the latest version to benefit from the latest security updates.

## Reporting a Vulnerability

We take the security of our software very seriously. If you discover a security vulnerability, please follow these guidelines:

### How to Report

Please **DO NOT** create a public issue for security vulnerabilities.

Instead, report security vulnerabilities by emailing:

📧 **Email**: `modstart@163.com`

### What to Include

When reporting a security vulnerability, please include:

1. **Description**: A clear description of the vulnerability
2. **Steps to Reproduce**: Detailed steps to reproduce the issue
3. **Impact Assessment**: Your assessment of the potential impact
4. **Affected Versions**: Which versions are affected
5. **Proof of Concept**: If applicable, include a PoC or example exploit
6. **Suggested Fix**: If you have ideas on how to fix it (optional)

### Response Process

- **Initial Response**: We aim to respond within 48 hours
- **Status Updates**: We will keep you informed about the progress
- **Disclosure Coordination**: We will coordinate with you on the disclosure timeline
- **Credit**: We will credit you in the release notes (unless you prefer to remain anonymous)

### Responsible Disclosure

We ask that you:

- Give us reasonable time to fix the vulnerability before public disclosure
- Avoid exploiting the vulnerability beyond what is necessary to demonstrate it
- Do not access, modify, or delete data belonging to others
- Do not perform actions that could harm the availability of our services

## Security Updates

Security updates will be announced through:

- GitHub Releases
- Project Documentation
- Email notification to users who have reported issues

## Acknowledgments

We appreciate the security research community and welcome responsible disclosure of security vulnerabilities.



================================================
FILE: cli/cmd/plugin.go
================================================
package cmd

import (
	"focusany-cli/internal"

	"github.com/spf13/cobra"
)

var pluginCmd = &cobra.Command{
	Use:   "plugin",
	Short: "Manage plugins",
}

var pluginListCmd = &cobra.Command{
	Use:   "list",
	Short: "List all installed plugins",
	RunE: func(cmd *cobra.Command, args []string) error {
		cfg, err := internal.LoadAuthConfig()
		if err != nil {
			return err
		}
		result, err := internal.DoRequest(cfg, "GET", "/api/plugin/list", nil)
		if err != nil {
			return err
		}
		return internal.PrintJSON(result)
	},
}

func init() {
	pluginCmd.AddCommand(pluginListCmd)
}


================================================
FILE: cli/cmd/root.go
================================================
package cmd

import (
	"os"

	"github.com/spf13/cobra"
)

var appVersion string

// Execute sets the version and runs the root command.
func Execute(version string) {
	appVersion = version
	if err := rootCmd.Execute(); err != nil {
		os.Exit(1)
	}
}

var rootCmd = &cobra.Command{
	Use:   "focusany",
	Short: "FocusAny CLI",
	Long:  "FocusAny command-line tool for interacting with the local FocusAny service.",
}

func init() {
	rootCmd.AddCommand(versionCmd)
	rootCmd.AddCommand(pluginCmd)
}


================================================
FILE: cli/cmd/version.go
================================================
package cmd

import (
	"focusany-cli/internal"

	"github.com/spf13/cobra"
)

var versionCmd = &cobra.Command{
	Use:   "version",
	Short: "Print version information",
	RunE: func(cmd *cobra.Command, args []string) error {
		return internal.PrintJSON(map[string]string{
			"version": appVersion,
		})
	},
}


================================================
FILE: cli/go.mod
================================================
module focusany-cli

go 1.22

require github.com/spf13/cobra v1.8.0

require (
	github.com/inconshreveable/mousetrap v1.1.0 // indirect
	github.com/spf13/pflag v1.0.5 // indirect
)


================================================
FILE: cli/go.sum
================================================
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0=
github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=


================================================
FILE: cli/internal/client.go
================================================
package internal

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

// DoRequest sends an HTTP request to the local FocusAny HTTP server.
func DoRequest(cfg *AuthConfig, method string, urlPath string, body any) (map[string]any, error) {
	var reqBody io.Reader
	if body != nil {
		b, err := json.Marshal(body)
		if err != nil {
			return nil, fmt.Errorf("marshal request body: %w", err)
		}
		reqBody = bytes.NewReader(b)
	}

	url := fmt.Sprintf("http://127.0.0.1:%d%s", cfg.Port, urlPath)
	req, err := http.NewRequest(method, url, reqBody)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	req.Header.Set("Authorization", "Bearer "+cfg.Token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("request failed: %w (is FocusAny running?)", err)
	}
	defer resp.Body.Close()

	respBytes, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read response: %w", err)
	}

	if resp.StatusCode == http.StatusUnauthorized {
		return nil, fmt.Errorf("unauthorized: token mismatch, restart FocusAny and try again")
	}

	var result map[string]any
	if err := json.Unmarshal(respBytes, &result); err != nil {
		return nil, fmt.Errorf("parse response: %w", err)
	}
	return result, nil
}

// PrintJSON outputs a value as indented JSON to stdout.
func PrintJSON(v any) error {
	b, err := json.MarshalIndent(v, "", "  ")
	if err != nil {
		return err
	}
	fmt.Println(string(b))
	return nil
}


================================================
FILE: cli/internal/config.go
================================================
package internal

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
)

// AuthConfig holds the port and token read from cli-auth.json
type AuthConfig struct {
	Port  int    `json:"port"`
	Token string `json:"token"`
}

// userDataDir returns the Electron userData directory path matching app.getPath('userData')
// which uses the app name "focusany".
func userDataDir() (string, error) {
	switch runtime.GOOS {
	case "darwin":
		home, err := os.UserHomeDir()
		if err != nil {
			return "", err
		}
		return filepath.Join(home, "Library", "Application Support", "focusany"), nil
	case "windows":
		appData := os.Getenv("APPDATA")
		if appData == "" {
			return "", fmt.Errorf("APPDATA environment variable not set")
		}
		return filepath.Join(appData, "focusany"), nil
	default:
		// Linux: XDG_CONFIG_HOME or ~/.config
		configDir := os.Getenv("XDG_CONFIG_HOME")
		if configDir == "" {
			home, err := os.UserHomeDir()
			if err != nil {
				return "", err
			}
			configDir = filepath.Join(home, ".config")
		}
		return filepath.Join(configDir, "focusany"), nil
	}
}

// LoadAuthConfig reads cli-auth.json from the focusany userData directory.
func LoadAuthConfig() (*AuthConfig, error) {
	dir, err := userDataDir()
	if err != nil {
		return nil, fmt.Errorf("cannot determine userData directory: %w", err)
	}
	filePath := filepath.Join(dir, "cli-auth.json")
	data, err := os.ReadFile(filePath)
	if err != nil {
		return nil, fmt.Errorf("cannot read %s: %w (is FocusAny running?)", filePath, err)
	}
	var cfg AuthConfig
	if err := json.Unmarshal(data, &cfg); err != nil {
		return nil, fmt.Errorf("invalid cli-auth.json: %w", err)
	}
	if cfg.Port == 0 || cfg.Token == "" {
		return nil, fmt.Errorf("cli-auth.json is incomplete (port=%d, token empty=%v)", cfg.Port, cfg.Token == "")
	}
	return &cfg, nil
}


================================================
FILE: cli/main.go
================================================
package main

import "focusany-cli/cmd"

// Version is injected at build time via ldflags: -X main.Version=x.x.x
var Version = "dev"

func main() {
	cmd.Execute(Version)
}


================================================
FILE: electron/config/common.ts
================================================
export const CommonConfig = {
    darkModeEnable: true,
    dbSystem: "system",
    dbConfigId: "config",
    dbDisabledActionMatchId: "disabledActionMatch",
    dbPinActionId: "pinAction",
    dbFileId: "file",
    dbLaunchId: "launch",
    dbCustomActionId: "customAction",
    dbHistoryActionId: "historyAction",
    dbPluginConfigId: "pluginConfig",
    dbPluginIdPrefix: "plugin",
    dbPluginStorageIdPrefix: "storage",
};


================================================
FILE: electron/config/contextMenu.ts
================================================
import contextMenu from "electron-context-menu";

const init = () => {
    contextMenu({
        showSaveImageAs: false,
        showCopyLink: false,
        showCopyImage: false,
        showSelectAll: false,
        showInspectElement: false,
        showSearchWithGoogle: false,
        showLookUpSelection: false,
    });
};

export const ConfigContextMenu = {
    init,
};


================================================
FILE: electron/config/icon.ts
================================================
import { buildResolve, extraResolve } from "../lib/env";

export const logoPath = buildResolve("logo.png");
export const icoLogoPath = buildResolve("logo.ico");
export const icnsLogoPath = buildResolve("logo.icns");

export const trayPath =
    process.platform === "darwin"
        ? extraResolve("osx/tray/iconTemplate.png")
        : extraResolve("common/tray/icon.png");


================================================
FILE: electron/config/lang.ts
================================================
import enUS from "./../../src/lang/en-US.json";
import zhCN from "./../../src/lang/zh-CN.json";
import { isDev } from "../lib/env";
import { ConfigMain } from "../mapi/config/main";

export const defaultLocale = "zh-CN";

let locale = defaultLocale;

export const langMessageList = [
    {
        name: "en-US",
        label: "English",
        messages: enUS,
    },
    {
        name: "zh-CN",
        label: "简体中文",
        messages: zhCN,
    },
];

const buildMessages = (): any => {
    let messages = {};
    for (let m of langMessageList) {
        messages[m.name] = m.messages;
    }
    return messages;
};

let messages = buildMessages();

export const t = (text: string, param: object | null = null) => {
    if (messages[locale]) {
        if (messages[locale][text]) {
            if (param) {
                return messages[locale][text].replace(
                    /\{(\w+)\}/g,
                    function (match, key) {
                        return key in param ? param[key] : match;
                    },
                );
            }
            return messages[locale][text];
        }
    }
    if (param) {
        return text.replace(/\{(\w+)\}/g, function (match, key) {
            return key in param ? param[key] : match;
        });
    }
    return text;
};

const readyAsync = async () => {
    locale = await ConfigMain.get("lang", defaultLocale);
};

const getLocale = () => {
    return locale;
};

export const ConfigLang = {
    readyAsync,
    getLocale,
};


================================================
FILE: electron/config/menu.ts
================================================
import { app, Menu } from "electron";
import { isDev, isMac } from "../lib/env";
import { t } from "./lang";

let contextMenu: Electron.Menu;

const ready = () => {
    const menuTemplate: Electron.MenuItemConstructorOptions[] = [];
    if (isMac) {
        menuTemplate.push({
            label: app.name,
            submenu: [
                { label: `${t("menu.about")}${app.name}`, role: "about" },
                { type: "separator" },
                // {
                //     label: t("设置"),
                //     click: () => {
                //         createSettingWindow();
                //     },
                //     accelerator: "CmdOrCtrl+,",
                // },
                // {type: "separator"},
                { label: t("menu.services"), role: "services" },
                { type: "separator" },
                { label: `${t("menu.hide")} ${app.name}`, role: "hide" },
                { label: t("menu.hideOthers"), role: "hideOthers" },
                { label: t("menu.showAll"), role: "unhide" },
                { type: "separator" },
                { label: t("menu.quit"), role: "quit" },
            ],
        });
    }
    menuTemplate.push({
        label: t("menu.edit"),
        submenu: [
            { label: t("menu.undo"), accelerator: "CmdOrCtrl+Z", role: "undo" },
            {
                label: t("menu.redo"),
                accelerator: "Shift+CmdOrCtrl+Z",
                role: "redo",
            },
            { type: "separator" },
            { label: t("menu.cut"), accelerator: "CmdOrCtrl+X", role: "cut" },
            { label: t("menu.copy"), accelerator: "CmdOrCtrl+C", role: "copy" },
            {
                label: t("menu.paste"),
                accelerator: "CmdOrCtrl+V",
                role: "paste",
            },
            {
                label: t("menu.selectAll"),
                accelerator: "CmdOrCtrl+A",
                role: "selectAll",
            },
        ],
    });
    if (isDev) {
        menuTemplate.push({
            label: t("menu.view"),
            submenu: [
                { label: t("menu.reload"), role: "reload" },
                { label: t("menu.forceReload"), role: "forceReload" },
                { label: t("menu.devTools"), role: "toggleDevTools" },
                { type: "separator" },
                {
                    label: t("menu.actualSize"),
                    role: "resetZoom",
                    accelerator: "",
                },
                { label: t("menu.zoomIn"), role: "zoomIn" },
                { label: t("menu.zoomOut"), role: "zoomOut" },
                { type: "separator" },
                { label: t("menu.fullscreen"), role: "togglefullscreen" },
            ],
        });
    }
    // menuTemplate.push({
    //     label: t("帮助"),
    //     role: "help",
    //     submenu: [
    //         // {
    //         //     label: t("教程帮助"),
    //         //     click: () => {
    //         //         createHelpWindow();
    //         //     },
    //         // },
    //         // {type: "separator"},
    //         // {
    //         //     label: t("关于"),
    //         //     click: () => {
    //         //         PageAbout.open().then()
    //         //     },
    //         // },
    //     ],
    // })
    const menu = Menu.buildFromTemplate(menuTemplate);
    Menu.setApplicationMenu(menu);
};

export const ConfigMenu = {
    ready,
};


================================================
FILE: electron/config/tray.ts
================================================
import { app, Menu, shell, Tray } from "electron";
import { trayPath } from "./icon";
import { AppRuntime } from "../mapi/env";
import { AppConfig } from "../../src/config";
import { t } from "./lang";
import { isMac, isWin } from "../lib/env";
import { AppsMain } from "../mapi/app/main";

let tray = null;

const showApp = () => {
    AppRuntime.mainWindow.show();
    AppRuntime.mainWindow.focus();
};

const hideApp = () => {
    if (isMac) {
        app.dock.hide();
    }
    AppRuntime.mainWindow.hide();
};

const quitApp = () => {
    (app as any).forceQuit = true;
    app.quit();
};

const ready = () => {
    const contextMenu = Menu.buildFromTemplate([
        {
            label: t("tray.showMain"),
            click: () => {
                showApp();
            },
        },
        {
            label: t("nav.guide"),
            click: () => {
                AppsMain.windowOpen("guide").then();
            },
        },
        {
            label: t("tray.visitWebsite"),
            click: () => {
                shell.openExternal(AppConfig.website);
            },
        },
        { type: "separator" },
        {
            label: t("tray.restart"),
            click: () => {
                app.relaunch();
                quitApp();
            },
        },
        {
            label: t("menu.quit"),
            click: () => {
                quitApp();
            },
        },
        { type: "separator" },
        {
            label: t("about.title"),
            click: () => {
                AppsMain.windowOpen("about").then();
            },
        },
    ]);
    tray = new Tray(trayPath);
    tray.setToolTip(AppConfig.name);
    tray.on("click", () => {
        showApp();
    });
    tray.on("right-click", () => {
        tray.popUpContextMenu(contextMenu);
    });
};

const show = () => {
    if (tray) {
        tray.destroy();
        tray = null;
    }
};

export const ConfigTray = {
    ready,
};


================================================
FILE: electron/config/window.ts
================================================
export const WindowConfig = {
    alwaysOpenDevTools: true,
    minWidth: 800,
    minHeight: 60,
    initWidth: 800,
    initHeight: 60,
    mainHeight: 60,
    mainWidth: 800,
    mainMaxHeight: 600,
    pluginWidth: 800,
    pluginHeight: 500,
    aboutWidth: 500,
    aboutHeight: 400,
    logWidth: 800,
    logHeight: 600,
    feedbackWidth: 700,
    feedbackHeight: 600,
    guideWidth: 800,
    guideHeight: 540,
    paymentWidth: 500,
    paymentHeight: 400,
    setupWidth: 800,
    setupHeight: 540,
    fastPanelWidth: 260,
    fastPanelHeight: 500,
    detachWindowTitleHeight: 40,
};


================================================
FILE: electron/declarations/electron.d.ts
================================================
declare module "electron" {
    interface BrowserView {
        _window?: any;
        _plugin?: any;
    }

    interface BrowserWindow {
        _name?: string;
        _plugin?: any;
        _type?: "action" | "callPage";
    }
}


================================================
FILE: electron/declarations/svg.d.ts
================================================
declare module "*.svg" {
    const content: string;
    export default content;
}


================================================
FILE: electron/electron-env.d.ts
================================================
/// <reference types="vite-plugin-electron/electron-env" />
/// <reference types="../sdk/focusany" />

declare namespace NodeJS {
    interface ProcessEnv {
        /**
         * The built directory structure
         *
         * ```tree
         * ├─┬ dist-electron
         * │ ├─┬ main
         * │ │ └── index.js    > Electron-Main
         * │ └─┬ preload
         * │   └── index.mjs   > Preload-Scripts
         * ├─┬ dist
         * │ └── index.html    > Electron-Renderer
         * ```
         */
        APP_ROOT: string;
        /** /dist/ or /public/ */
        VITE_PUBLIC: string;
    }
}


================================================
FILE: electron/lib/api.ts
================================================
import Apps from "../mapi/app";

export type ResultType<T> = {
    // should follow the rules:
    // <0 business error
    // =0 success
    // 10000 error ( network error, server error, etc. )
    code: number;
    msg: string;
    data?: T;
};

export const post = async (url: string, data: any) => {
    data = data || {};
    const userAgent = Apps.getUserAgent();
    data["AppManagerUserAgent"] = userAgent;
    return await fetch(url, {
        method: "POST",
        headers: {
            "User-Agent": userAgent,
            "Content-Type": "application/json",
        },
        body: JSON.stringify(data),
    });
};


================================================
FILE: electron/lib/devtools.ts
================================================
import { BrowserView, BrowserWindow, screen } from "electron";
import { isDev } from "./env";
import { WindowConfig } from "../config/window";

export const DevToolsManager = {
    enable: true,
    rowCount: 4,
    colCount: 3,
    windows: new Map<BrowserWindow | BrowserView, BrowserWindow>(),
    setEnable(enable: boolean) {
        DevToolsManager.enable = enable;
    },
    getWindow(win: BrowserWindow | BrowserView) {
        return this.windows.get(win);
    },
    getOrCreateWindow(name: string, win: BrowserWindow | BrowserView) {
        if (this.windows.has(win)) {
            return this.windows.get(win);
        }
        const { x, y, width, height } = this.getDisplayPosition();
        // console.log('DevToolsManager', name, {x, y, width, height})
        const devtools = new BrowserWindow({
            show: true,
            x,
            y,
            width,
            height,
            title: name,
        });
        devtools.on("closed", (e) => {
            // console.log('DevToolsManager', 'close', name)
            this.windows.delete(win);
        });
        // console.log('DevToolsManager', name, {x, y})
        win.webContents.setDevToolsWebContents(devtools.webContents);
        win.webContents.on("destroyed", () => {
            // console.log('DevToolsManager', 'destroyed', name)
            devtools.destroy();
        });
        devtools.webContents.on("dom-ready", () => {
            setTimeout(() => {
                if (!devtools.isDestroyed()) {
                    devtools.setTitle(name);
                }
            }, 1000);
        });
        this.windows.set(win, devtools);
        return devtools;
    },
    getLargestDisplay(): Electron.Display {
        const displays = screen.getAllDisplays();
        return displays.reduce((max, display) => {
            const { width, height } = display.size;
            const maxResolution = max.size.width * max.size.height;
            const currentResolution = width * height;
            return currentResolution > maxResolution ? display : max;
        });
    },
    getDisplayPosition(): {
        x: number;
        y: number;
        width: number;
        height: number;
    } {
        const display = this.getLargestDisplay();
        const { x, y, width, height } = display.workArea;
        // console.log('DevToolsManager', 'getDisplayPosition', {x, y, width, height})
        if (width < 1300) {
            this.rowCount = 3;
            this.colCount = 2;
        }
        const itemWidth = Math.floor(width / this.rowCount);
        const itemHeight = Math.floor(height / this.colCount);
        const maxRow = Math.floor(width / itemWidth);
        const row = this.windows.size % maxRow;
        const col = Math.floor(this.windows.size / maxRow);
        return {
            x: x + row * itemWidth,
            y: y + col * itemHeight,
            width: itemWidth,
            height: itemHeight,
        };
    },
    register(name: string, win: BrowserWindow | BrowserView) {
        if (!isDev || !DevToolsManager.enable) {
            return;
        }
        this.getOrCreateWindow(name, win);
    },
    autoShow(win: BrowserWindow | BrowserView) {
        if (!isDev || !DevToolsManager.enable) {
            return;
        }
        if (WindowConfig.alwaysOpenDevTools) {
            win.webContents.openDevTools({
                mode: "detach",
                activate: false,
            });
        }
    },
};


================================================
FILE: electron/lib/env-main.ts
================================================
import url, { fileURLToPath } from "node:url";
import { BrowserView, BrowserWindow } from "electron";
import { isPackaged } from "./env";
import path, { join } from "node:path";
import { Log } from "../mapi/log/main";

const __dirname = path.dirname(fileURLToPath(import.meta.url));

process.env.APP_ROOT = path.join(__dirname, "../..");

export const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
export const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
export const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;

process.env.VITE_PUBLIC = VITE_DEV_SERVER_URL
    ? path.join(process.env.APP_ROOT, "public")
    : RENDERER_DIST;

export const preloadDefault = path.join(MAIN_DIST, "preload/index.cjs");

export const preloadPluginDefault = path.join(
    MAIN_DIST,
    "preload-plugin/plugin.cjs",
);

export const rendererLoadPath = (
    window: BrowserWindow | BrowserView,
    fileName: string,
) => {
    if (!isPackaged && process.env.VITE_DEV_SERVER_URL) {
        const x = new url.URL(rendererDistPath(fileName));
        // console.log('rendererLoadPath', fileName, x.toString())
        if (window instanceof BrowserView) {
            window.webContents.loadURL(x.toString());
        } else {
            window.loadURL(x.toString());
        }
    } else {
        // console.log('rendererLoadPath', fileName, rendererDistPath(fileName))
        if (window instanceof BrowserView) {
            window.webContents.loadFile(rendererDistPath(fileName));
        } else {
            window.loadFile(rendererDistPath(fileName));
        }
    }
};

export const rendererDistPath = (fileName: string) => {
    if (!isPackaged && process.env.VITE_DEV_SERVER_URL) {
        return `${process.env.VITE_DEV_SERVER_URL.replace(/\/+$/, "")}/${fileName}`;
    }
    return join(RENDERER_DIST, fileName);
};

export const rendererIsUrl = (url: string) => {
    return (
        url.startsWith("http://") ||
        url.startsWith("https://") ||
        url.startsWith("file://")
    );
};

export const getGpuInfo = async () => {
    const list = [] as {
        index: number;
        name: string;
        size: number;
    }[];
    try {
        // @ts-ignore
        const si = await import("systeminformation");
        const graphics = await si.graphics();
        graphics.controllers.forEach((controller, index) => {
            list.push({
                index,
                name: controller.model,
                size: Math.ceil(controller.vram / 1024),
            });
        });
    } catch (e) {
        Log.error("getGpuInfo", e);
    }
    return list;
};


================================================
FILE: electron/lib/env.ts
================================================
import { execSync } from "child_process";
import { resolve } from "node:path";
import fs from "node:fs";
import os from "os";
import { Log } from "../mapi/log";
import FileIndex from "../mapi/file";

export const isPackaged = ["true"].includes(process.env.IS_PACKAGED);

export const isDev = !isPackaged;

export const isWin = process.platform === "win32";

export const isMac = process.platform === "darwin";

export const isLinux = process.platform === "linux";

export const isMain = process.type === "browser";

export const isRender = process.type === "renderer";

export const platformName = (): "win" | "osx" | "linux" | null => {
    if (isWin) return "win";
    if (isMac) return "osx";
    if (isLinux) return "linux";
    return null;
};

export const memoryInfo = () => {
    return {
        total: os.totalmem(),
        free: os.freemem(),
    };
};

const tryFirst = (functionList: (() => any)[]) => {
    for (const fun of functionList) {
        try {
            return fun();
        } catch (e) {}
    }
    return null;
};

let platformVersionCache: string | null = null;
export const platformVersion = () => {
    if (null === platformVersionCache) {
        const functionList: any[] = [];
        if (isWin) {
            functionList.push(() =>
                execSync("wmic os get Version")
                    .toString()
                    .split("\n")[1]
                    .trim(),
            );
            functionList.push(() =>
                execSync(
                    "powershell -command \"(Get-ItemProperty 'HKLM:\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion').ReleaseId\"",
                )
                    .toString()
                    .trim(),
            );
        } else if (isMac) {
            functionList.push(() =>
                execSync("sw_vers -productVersion").toString().trim(),
            );
        } else if (isLinux) {
            functionList.push(() =>
                execSync("cat /etc/os-release | grep VERSION_ID")
                    .toString()
                    .split("=")[1]
                    .trim()
                    .replace(/"/g, ""),
            );
        }
        platformVersionCache = tryFirst(functionList);
        if (!platformVersionCache) {
            Log.error("env.platformVersion.error");
            platformVersionCache = "0.0.0";
        }
    }
    return platformVersionCache;
};

export const platformArch = (): "x86" | "arm64" | null => {
    switch (os.arch()) {
        case "x64":
            return "x86";
        case "arm64":
            return "arm64";
    }
    return null;
};

let platformUUIDCache: string | null = null;
export const platformUUID = () => {
    if (null === platformUUIDCache) {
        const functionList: any[] = [];
        if (isWin) {
            functionList.push(() =>
                execSync("wmic csproduct get UUID")
                    .toString()
                    .split("\n")[1]
                    .trim(),
            );
            functionList.push(() =>
                execSync(
                    'powershell -command "(Get-WmiObject Win32_ComputerSystemProduct).UUID"',
                )
                    .toString()
                    .trim(),
            );
        } else if (isMac) {
            functionList.push(() =>
                execSync("system_profiler SPHardwareDataType | grep UUID")
                    .toString()
                    .split(": ")[1]
                    .trim(),
            );
        } else if (isLinux) {
            functionList.push(() =>
                execSync("cat /var/lib/dbus/machine-id")
                    .toString()
                    .trim()
                    .toUpperCase(),
            );
        }
        platformUUIDCache = tryFirst(functionList);
        if (!platformUUIDCache) {
            Log.error("env.platformUUID.error");
            platformUUIDCache = "000000";
        }
    }
    return platformUUIDCache;
};

export const buildResolve = (value: string): string => {
    return resolve(`electron/resources/build/${value}`);
};

export const binResolve = (value: string): string => {
    return resolve(process.resourcesPath, "bin", value);
};

export const extraResolve = (filePath: string): string => {
    const basePath = isPackaged ? process.resourcesPath : "electron/resources";
    return resolve(basePath, "extra", filePath);
};

export const extraResolveWithPlatform = (filePath: string): string => {
    const dir = [platformName(), platformArch()].join("-");
    const p = [dir, filePath].join("/");
    return extraResolve(p);
};

export const extraResolveBin = (filePath: string): string => {
    if (isWin) {
        if (!filePath.endsWith(".exe")) {
            filePath += ".exe";
        }
    }
    const dir = [platformName(), platformArch()].join("-");
    const p = [dir, filePath].join("/");
    const binaryPath = extraResolve(p);
    if (!fs.existsSync(binaryPath)) {
        throw new Error(`Binary file not found: ${binaryPath}`);
    }
    return binaryPath;
};


================================================
FILE: electron/lib/hooks.ts
================================================
import { BrowserWindow } from "electron";

type HookType = never | "Show" | "Hide";

export const executeHooks = async (
    win: BrowserWindow,
    hook: HookType,
    data?: any,
) => {
    const evalJs = `
    if(window.__page && window.__page.hooks && typeof window.__page.hooks.on${hook} === 'function' ) {
        try {
            window.__page.hooks.on${hook}(${JSON.stringify(data)});
        } catch(e) {
            console.log('executeHooks.on${hook}.error', e);
        }
    }`;
    return win.webContents?.executeJavaScript(evalJs);
};


================================================
FILE: electron/lib/permission.ts
================================================
import { isMac } from "./env";

let nodeMacPermissions = null;
if (isMac) {
    (async () => {
        try {
            nodeMacPermissions = await import("node-mac-permissions");
            nodeMacPermissions = nodeMacPermissions.default;
            // console.log('nodeMacPermissions',nodeMacPermissions);
        } catch (e) {}
    })();
}

export const Permissions = {
    async checkAccessibilityAccess(): Promise<boolean> {
        return new Promise((resolve, reject) => {
            if (isMac) {
                const status =
                    nodeMacPermissions.getAuthStatus("accessibility");
                resolve(status === "authorized");
            } else {
                resolve(true);
            }
        });
    },
    async askAccessibilityAccess() {
        nodeMacPermissions.askForAccessibilityAccess();
    },
    async checkScreenCaptureAccess(): Promise<boolean> {
        return new Promise((resolve, reject) => {
            if (isMac) {
                const status = nodeMacPermissions.getAuthStatus("screen");
                resolve(status === "authorized");
            } else {
                resolve(true);
            }
        });
    },
    async askScreenCaptureAccess() {
        nodeMacPermissions.askForScreenCaptureAccess(true);
    },
};


================================================
FILE: electron/lib/pinyin-util.ts
================================================
import PinyinMatch from "pinyin-match";

export const PinyinUtil = {
    match(input, keywords) {
        const index = PinyinMatch.match(input, keywords);
        let inputMark = input;
        let similarity = 0;
        if (index) {
            const indexStart = index[0];
            const indexEnd = index[1];
            inputMark =
                input.substring(0, indexStart) +
                "<mark>" +
                input.substring(indexStart, indexEnd + 1) +
                "</mark>" +
                input.substring(indexEnd + 1);
            similarity = (indexEnd - indexStart + 1) / input.length;
        }
        return {
            matched: !!index,
            inputMark,
            similarity,
        };
    },
    mark(text) {
        return `<mark>${text}</mark>`;
    },
};


================================================
FILE: electron/lib/process.ts
================================================
/** 在主进程中获取关键信息存储到环境变量中,从而在预加载脚本中及渲染进程中使用 */
import { app } from "electron";

/** 注意: app.isPackaged 可能被被某些方法改变所以请将该文件放到 main.js 必须位于非依赖项的顶部 */
import fixPath from "fix-path";

if (process.platform === "darwin") {
    fixPath();
}

process.env.IS_PACKAGED = String(app.isPackaged);

process.env.DESKTOP_PATH = app.getPath("desktop");

process.env.CWD = process.cwd();

export const isDummy = false;


================================================
FILE: electron/lib/util.ts
================================================
import chardet from "chardet";
import dayjs from "dayjs";
import iconvLite from "iconv-lite";
import { Base64 } from "js-base64";
import * as crypto from "node:crypto";
import fs from "node:fs";
import path from "node:path";
import Showdown from "showdown";
import { Iconv } from "iconv";
import { isMac, isWin } from "./env";
import FileIndex from "../mapi/file";

export const sleep = (time = 1000) => {
    return new Promise((resolve) => {
        setTimeout(() => resolve(true), time);
    });
};

export const EncodeUtil = {
    base32Alphabet: "abcdefghijklmnopqrstuvwxyz234567",
    base32Encode(str: string) {
        const buffer = Buffer.from(str, "utf8");
        let bits = "";
        let output = "";
        // 将每个字节转为8位二进制
        for (let i = 0; i < buffer.length; i++) {
            const byte = buffer[i];
            bits += byte.toString(2).padStart(8, "0");
        }
        // 每5位一组,转为 Base32 字符
        for (let i = 0; i < bits.length; i += 5) {
            const chunk = bits.slice(i, i + 5);
            const paddedChunk = chunk.padEnd(5, "0"); // 不足5位补0
            const index = parseInt(paddedChunk, 2);
            output += EncodeUtil.base32Alphabet[index];
        }
        return output;
    },
    base32Decode(str: string) {
        const base32Alphabet = "abcdefghijklmnopqrstuvwxyz234567";
        let bits = "";
        for (let i = 0; i < str.length; i++) {
            const char = str[i];
            const index = base32Alphabet.indexOf(char);
            if (index === -1) {
                throw new Error("Invalid Base32 character: " + char);
            }
            bits += index.toString(2).padStart(5, "0");
        }

        const bytes: number[] = [];
        for (let i = 0; i + 8 <= bits.length; i += 8) {
            const byte = bits.slice(i, i + 8);
            bytes.push(parseInt(byte, 2));
        }

        return Buffer.from(bytes).toString("utf8");
    },
    base64Encode(str: string) {
        return Base64.encode(str);
    },
    base64Decode(str: string) {
        return Base64.decode(str);
    },
    md5(str: string) {
        return crypto.createHash("md5").update(str).digest("hex");
    },
    aesEncode(str: string, key: string) {
        const cipher = crypto.createCipheriv("aes-128-ecb", key, "");
        let crypted = cipher.update(str, "utf8", "base64");
        crypted += cipher.final("base64");
        return crypted;
    },
    aesDecode(str: string, key: string) {
        const decipher = crypto.createDecipheriv("aes-128-ecb", key, "");
        let dec = decipher.update(str, "base64", "utf8");
        dec += decipher.final("utf8");
        return dec;
    },
    async fileXzipEncode(pathname: string): Promise<string> {
        if (!fs.existsSync(pathname)) {
            throw new Error(`Input file not found: ${pathname}`);
        }

        // Generate new filepath with .xzip extension
        const basePath = pathname.substring(0, pathname.lastIndexOf("."));
        const outputPath = basePath + ".xzip";

        // Get file info
        const fileStats = fs.statSync(pathname);
        const fileSize = fileStats.size;
        const fileExt = pathname.split(".").pop() || "";

        // Generate random 16-character key
        const encryptionKey = StrUtil.randomString(16);

        // Create metadata
        const filemeta = {
            version: 1,
            format: fileExt,
            size: fileSize,
            key: encryptionKey,
        };

        // Convert metadata to JSON and then base64 encode
        const metaJson = JSON.stringify(filemeta);
        const metaB64 = Buffer.from(metaJson, "utf-8").toString("base64");
        const metaLength = metaB64.length;

        // Prepare encryption key
        const keyBytes = Buffer.from(encryptionKey, "utf-8");
        const keyLength = keyBytes.length;

        // Stream processing: read, encrypt and write in chunks
        const inputStream = fs.createReadStream(pathname);
        const outputStream = fs.createWriteStream(outputPath);

        // Write metadata length (4 bytes, little-endian)
        const metaLengthBuffer = Buffer.allocUnsafe(4);
        metaLengthBuffer.writeUInt32LE(metaLength, 0);
        outputStream.write(metaLengthBuffer);

        // Write base64 encoded metadata
        outputStream.write(Buffer.from(metaB64, "utf-8"));

        // Stream encrypt the file content
        let bytesProcessed = 0;
        return new Promise((resolve, reject) => {
            inputStream.on("data", (chunk: Buffer) => {
                // XOR encrypt the chunk
                const encryptedChunk = Buffer.alloc(chunk.length);
                for (let i = 0; i < chunk.length; i++) {
                    encryptedChunk[i] =
                        chunk[i] ^ keyBytes[bytesProcessed % keyLength];
                    bytesProcessed++;
                }

                // Write encrypted chunk
                outputStream.write(encryptedChunk);
            });

            inputStream.on("end", () => {
                outputStream.end();
                resolve(outputPath);
            });

            inputStream.on("error", (error) => {
                outputStream.destroy();
                reject(error);
            });

            outputStream.on("error", (error) => {
                inputStream.destroy();
                reject(error);
            });
        });
    },
    async fileXzipDecode(pathname: string): Promise<string> {
        if (!fs.existsSync(pathname)) {
            throw new Error(`Input file not found: ${pathname}`);
        }

        if (!pathname.endsWith(".xzip")) {
            return pathname; // Not an xzip file, return as is
        }

        let outputPath = pathname.replace(/\.xzip$/, "");

        return new Promise((resolve, reject) => {
            const inputStream = fs.createReadStream(pathname);
            let metadataRead = false;
            let filemeta: any = null;
            let keyBytes: Buffer;
            let bytesProcessed = 0;
            let outputStream: fs.WriteStream;
            let remainingMetaBytes = 0;
            let metaBuffer = Buffer.alloc(0);

            inputStream.on("data", (chunk: Buffer) => {
                let chunkOffset = 0;

                if (!metadataRead) {
                    if (remainingMetaBytes === 0) {
                        // Read metadata length (first 4 bytes)
                        if (chunk.length < 4) {
                            reject(
                                new Error(
                                    "Invalid xzip file: insufficient data for metadata length",
                                ),
                            );
                            return;
                        }
                        const metaLength = chunk.readUInt32LE(0);
                        remainingMetaBytes = metaLength;
                        chunkOffset = 4;
                    }

                    // Read metadata
                    const availableMetaBytes = Math.min(
                        remainingMetaBytes,
                        chunk.length - chunkOffset,
                    );
                    const metaChunk = chunk.subarray(
                        chunkOffset,
                        chunkOffset + availableMetaBytes,
                    );
                    metaBuffer = Buffer.concat([
                        metaBuffer,
                        metaChunk,
                    ] as readonly Uint8Array[]);
                    remainingMetaBytes -= availableMetaBytes;
                    chunkOffset += availableMetaBytes;

                    if (remainingMetaBytes === 0) {
                        // Parse metadata
                        try {
                            const metaB64 = metaBuffer.toString("utf-8");
                            const metaJson = Buffer.from(
                                metaB64,
                                "base64",
                            ).toString("utf-8");
                            filemeta = JSON.parse(metaJson);
                            keyBytes = Buffer.from(filemeta.key, "utf-8");

                            // Create output file with correct extension
                            const finalOutputPath =
                                outputPath +
                                (filemeta.format ? "." + filemeta.format : "");
                            outputStream =
                                fs.createWriteStream(finalOutputPath);

                            metadataRead = true;

                            // Set the final output path for resolution
                            outputPath = finalOutputPath;
                        } catch (error) {
                            reject(
                                new Error(
                                    "Invalid xzip file: corrupted metadata",
                                ),
                            );
                            return;
                        }
                    }
                }

                if (metadataRead && chunkOffset < chunk.length) {
                    // Decrypt remaining chunk data
                    const encryptedChunk = chunk.subarray(chunkOffset);
                    const decryptedChunk = Buffer.alloc(encryptedChunk.length);
                    const keyLength = keyBytes.length;

                    for (let i = 0; i < encryptedChunk.length; i++) {
                        decryptedChunk[i] =
                            encryptedChunk[i] ^
                            keyBytes[bytesProcessed % keyLength];
                        bytesProcessed++;
                    }

                    outputStream.write(decryptedChunk);
                }
            });

            inputStream.on("end", () => {
                if (outputStream) {
                    outputStream.end();
                    resolve(outputPath);
                } else {
                    reject(new Error("Invalid xzip file: incomplete metadata"));
                }
            });

            inputStream.on("error", (error) => {
                if (outputStream) {
                    outputStream.destroy();
                }
                reject(error);
            });

            if (outputStream) {
                outputStream.on("error", (error) => {
                    inputStream.destroy();
                    reject(error);
                });
            }
        });
    },
};

export const IconvUtil = {
    convert(str: string, to?: string, from?: string) {
        if (!from) {
            from = chardet.detect(Buffer.from(str));
        }
        to = to || "utf8";
        const buffer = iconvLite.encode(str, from);
        return iconvLite.decode(buffer, to);
    },
    bufferToUtf8(buffer: Buffer) {
        const encoding = chardet.detect(buffer);
        // console.log('bufferToUtf8.encoding', encoding)
        if ("ISO-2022-CN" === encoding) {
            const iconvInstance = new Iconv(
                "ISO-2022-CN",
                "UTF-8//TRANSLIT//IGNORE",
            );
            return iconvInstance.convert(buffer).toString();
        }
        return iconvLite.decode(buffer, encoding).toString();
    },
    detect(buffer: Uint8Array) {
        // detect str encoding
        return chardet.detect(buffer);
    },
};

export const StrUtil = {
    randomString(len: number = 32) {
        const chars =
            "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        let result = "";
        for (let i = len; i > 0; --i) {
            result += chars[Math.floor(Math.random() * chars.length)];
        }
        return result;
    },
    uuid() {
        return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
            /[xy]/g,
            function (c) {
                const r = (Math.random() * 16) | 0;
                const v = c === "x" ? r : (r & 0x3) | 0x8;
                return v.toString(16);
            },
        );
    },
    hashCode(str: string, length: number = 8) {
        let hash = 0;
        if (str.length === 0) return hash + "";
        for (let i = 0; i < str.length; i++) {
            const char = str.charCodeAt(i);
            hash = (hash << 5) - hash + char;
            hash = hash & hash;
        }
        let result = Math.abs(hash).toString(16);
        if (result.length < length) {
            result = "0".repeat(length - result.length) + result;
        }
        return result;
    },
    hashCodeWithDuplicateCheck(
        str: string,
        check: string[],
        length: number = 8,
    ) {
        let code = this.hashCode(str, length);
        while (check.includes(code)) {
            code = this.uuid().substring(0, length);
        }
        return code;
    },
    bigIntegerId() {
        return [
            Date.now(),
            (Math.floor(Math.random() * 1000000) + "").padStart(6, "0"),
        ].join("");
    },
    ucFirst(str: string) {
        if (!str) return "";
        return str.charAt(0).toUpperCase() + str.slice(1);
    },
};

export const TimeUtil = {
    timestampInMs() {
        return Date.now();
    },
    timestamp() {
        return Math.floor(Date.now() / 1000);
    },
    format(time: number, format: string = "YYYY-MM-DD HH:mm:ss") {
        return dayjs(time).format(format);
    },
    formatDate(time: number) {
        return dayjs(time).format("YYYY-MM-DD");
    },
    dateString() {
        return dayjs().format("YYYYMMDD");
    },
    datetimeString() {
        return dayjs().format("YYYYMMDD_HHmmss_SSS");
    },
    timestampDayStart(msTimestamp?: number) {
        let date = msTimestamp ? new Date(msTimestamp) : new Date();
        date.setHours(0, 0, 0, 0);
        return Math.floor(date.getTime() / 1000);
    },
    replacePattern(text: string) {
        // @ts-ignore
        return text
            .replaceAll("{year}", dayjs().format("YYYY"))
            .replaceAll("{month}", dayjs().format("MM"))
            .replaceAll("{day}", dayjs().format("DD"))
            .replaceAll("{hour}", dayjs().format("HH"))
            .replaceAll("{minute}", dayjs().format("mm"))
            .replaceAll("{second}", dayjs().format("ss"));
    },
};

export const FileUtil = {
    MIME_TYPES: {
        html: "text/html",
        htm: "text/html",
        js: "application/javascript",
        css: "text/css",
        json: "application/json",
        png: "image/png",
        jpg: "image/jpeg",
        jpeg: "image/jpeg",
        gif: "image/gif",
        svg: "image/svg+xml",
        webp: "image/webp",
        woff: "font/woff",
        woff2: "font/woff2",
        ttf: "font/ttf",
        otf: "font/otf",
        mp3: "audio/mpeg",
        mp4: "video/mp4",
        wav: "audio/wav",
        wasm: "application/wasm",
        eot: "application/vnd.ms-fontobject",
    },
    getMimeByExt(ext: string, defaultMime: string = ""): string {
        ext = ext.toLowerCase();
        if (ext.startsWith(".")) {
            ext = ext.substring(1);
        }
        return FileUtil.MIME_TYPES[ext] || defaultMime;
    },
    getMimeByPath(p: string, defaultMime: string = ""): string {
        const extension = p.split(".").pop().toLowerCase();
        return FileUtil.getMimeByExt(extension, defaultMime);
    },
    streamToBase64(stream: NodeJS.ReadableStream): Promise<string> {
        return new Promise((resolve, reject) => {
            const chunks = [];
            stream.on("data", (chunk) => {
                chunks.push(chunk);
            });
            stream.on("end", () => {
                const buffer = Buffer.concat(chunks);
                resolve(buffer.toString("base64"));
            });
            stream.on("error", (error) => {
                reject(error);
            });
        });
    },
    bufferToBase64(buffer: Buffer) {
        let binary = "";
        let bytes = new Uint8Array(buffer);
        let len = bytes.byteLength;
        for (let i = 0; i < len; i++) {
            binary += String.fromCharCode(bytes[i]);
        }
        return EncodeUtil.base64Encode(binary);
    },
    base64ToBuffer(base64: string): Buffer {
        if (base64.startsWith("data:")) {
            base64 = base64.split("base64,")[1];
        }
        return Buffer.from(base64, "base64");
    },
    formatSize(size: number) {
        if (size < 1024) {
            return size + "B";
        } else if (size < 1024 * 1024) {
            return (size / 1024).toFixed(2) + "KB";
        } else if (size < 1024 * 1024 * 1024) {
            return (size / 1024 / 1024).toFixed(2) + "MB";
        } else {
            return (size / 1024 / 1024 / 1024).toFixed(2) + "GB";
        }
    },
    async md5(filePath: string) {
        return new Promise((resolve, reject) => {
            const hash = crypto.createHash("md5");
            const stream = fs.createReadStream(filePath);
            stream.on("data", (data) => {
                hash.update(data);
            });
            stream.on("end", () => {
                resolve(hash.digest("hex"));
            });
            stream.on("error", (error) => {
                reject(error);
            });
        });
    },
};

export const JsonUtil = {
    stringifyOrdered(obj: any) {
        return JSON.stringify(obj, Object.keys(obj).sort(), 4);
    },
    stringifyValueOrdered(obj: any) {
        const sortedData = Object.fromEntries(
            Object.entries(obj).sort(([, a], [, b]) => {
                // @ts-ignore
                return ((a as any) - b) as any;
            }),
        );
        return JSON.stringify(sortedData, null, 4);
    },
};

export const ImportUtil = {
    async loadCommonJs(cjsPath: string, forceReload: boolean = true) {
        let tempPath = cjsPath;
        if (forceReload) {
            const md5 = await FileUtil.md5(cjsPath);
            tempPath = path.join(
                await FileIndex.tempDir("commonJs"),
                `${md5}.cjs`,
            );
            if (!fs.existsSync(tempPath)) {
                fs.copyFileSync(cjsPath, tempPath);
            }
        }
        const backend = await import(/* @vite-ignore */ `file://${tempPath}`);
        // console.log('loadCommonJs', `file://${cjsPath}?t=${md5}`)
        return backend.default;
    },
};

export const MemoryCacheUtil = {
    pool: {} as {
        [key: string]: {
            value: any;
            expire: number;
        };
    },
    _gc() {
        const now = TimeUtil.timestamp();
        for (const key in this.pool) {
            if (this.pool[key].expire < now) {
                delete this.pool[key];
            }
        }
    },
    async remember<T extends any>(
        key: string,
        callback: () => Promise<any>,
        ttl: number = 3600,
    ) {
        if (this.pool[key] && this.pool[key].expire > TimeUtil.timestamp()) {
            return this.pool[key].value as T;
        }
        const value = await callback();
        this.pool[key] = {
            value,
            expire: TimeUtil.timestamp() + ttl,
        };
        this._gc();
        return value as T;
    },
    get(key: string) {
        if (this.pool[key] && this.pool[key].expire > TimeUtil.timestamp()) {
            return this.pool[key].value;
        }
        this._gc();
        return null;
    },
    set(key: string, value: any, ttl: number = 86400) {
        this.pool[key] = {
            value,
            expire: TimeUtil.timestamp() + ttl,
        };
        this._gc();
    },
    forget(key: string) {
        delete this.pool[key];
    },
};

export const MemoryMapCacheUtil = {
    pool: {} as {
        [group: string]: {
            [key: string]: {
                value: any;
                expire: number;
            };
        };
    },
    _gc() {
        const now = TimeUtil.timestamp();
        for (const group in this.pool) {
            for (const key in this.pool[group]) {
                if (this.pool[group][key].expire < now) {
                    delete this.pool[group][key];
                }
            }
        }
    },
    get(group: string, key: string) {
        if (
            this.pool[group] &&
            this.pool[group][key] &&
            this.pool[group][key].expire > TimeUtil.timestamp()
        ) {
            return this.pool[group][key].value;
        }
        this._gc();
        return null;
    },
    set(group: string, key: string, value: any, ttl: number = 86400) {
        if (!this.pool[group]) {
            this.pool[group] = {};
        }
        this.pool[group][key] = {
            value,
            expire: TimeUtil.timestamp() + ttl,
        };
        this._gc();
    },
    forget(group: string, key: string) {
        if (this.pool[group]) {
            delete this.pool[group][key];
        }
    },
};

export const ShellUtil = {
    quotaPath(p: string) {
        return `"${p}"`;
    },
    parseCommandArgs(command: string) {
        let args = [];
        let arg = "";
        let quote = "";
        let escape = false;
        for (let i = 0; i < command.length; i++) {
            const c = command[i];
            if (escape) {
                arg += c;
                escape = false;
                continue;
            }
            if ("\\" === c) {
                escape = true;
                arg += c;
                continue;
            }
            if ("" === quote && (" " === c || "\t" === c)) {
                if (arg) {
                    args.push(arg);
                    arg = "";
                }
                continue;
            }
            if ("" === quote && ('"' === c || "'" === c)) {
                quote = c;
                arg += c;
                continue;
            }
            if ('"' === quote && '"' === c) {
                quote = "";
                arg += c;
                continue;
            }
            if ("'" === quote && "'" === c) {
                quote = "";
                arg += c;
                continue;
            }
            arg += c;
        }
        if (arg) {
            args.push(arg);
        }
        return args;
    },
};

export const VersionUtil = {
    /**
     * 检测版本是否匹配
     * @param v string
     * @param match string 如 * 或 >=1.0.0 或 >1.0.0 或 <1.0.0 或 <=1.0.0 或 1.0.0
     */
    match(v: string, match: string) {
        if (match === "*") {
            return true;
        }
        if (match.startsWith(">=")) {
            if (this.ge(v, match.substring(2))) {
                return true;
            }
        } else if (match.startsWith("<=")) {
            if (this.le(v, match.substring(2))) {
                return true;
            }
        } else if (match.startsWith(">")) {
            if (this.gt(v, match.substring(1))) {
                return true;
            }
        } else if (match.startsWith("<")) {
            if (this.lt(v, match.substring(1))) {
                return true;
            }
        } else {
            return this.eq(v, match);
        }
        return false;
    },
    compare(v1: string, v2: string) {
        const v1Arr = v1.split(".");
        const v2Arr = v2.split(".");
        for (let i = 0; i < v1Arr.length; i++) {
            const v1Num = parseInt(v1Arr[i]);
            const v2Num = parseInt(v2Arr[i]);
            if (v1Num > v2Num) {
                return 1;
            } else if (v1Num < v2Num) {
                return -1;
            }
        }
        return 0;
    },
    gt(v1: string, v2: string) {
        return VersionUtil.compare(v1, v2) > 0;
    },
    ge(v1: string, v2: string) {
        return VersionUtil.compare(v1, v2) >= 0;
    },
    lt(v1: string, v2: string) {
        return VersionUtil.compare(v1, v2) < 0;
    },
    le: (v1: string, v2: string) => {
        return VersionUtil.compare(v1, v2) <= 0;
    },
    eq: (v1: string, v2: string) => {
        return VersionUtil.compare(v1, v2) === 0;
    },
};

export const UIUtil = {
    sizeToPx(size: string, sizeFull: number) {
        if (/^\d+$/.test(size)) {
            // 纯数字
            return parseInt(size);
        } else if (size.endsWith("%")) {
            // 百分比
            let result = Math.floor((sizeFull * parseInt(size)) / 100);
            result = Math.min(result, sizeFull);
            return result;
        } else {
            throw "UnsupportSizeString";
        }
    },
};

export const ReUtil = {
    match(regex: string, text: string) {
        if ("" === regex || null === regex) {
            return false;
        }
        if (regex.startsWith("/")) {
            const index = regex.lastIndexOf("/");
            const source = regex.slice(1, index);
            const flags = regex.slice(index + 1);
            return new RegExp(source, flags).test(text);
        }
        return new RegExp(regex).test(text);
    },
};

const converter = new Showdown.Converter({
    tables: true,
});
export const MarkdownUtil = {
    toHtml(markdown: string): string {
        return converter.makeHtml(markdown);
    },
};

type HotkeyModifierType =
    | "Control"
    | "Option"
    | "Command"
    | "Ctrl"
    | "Alt"
    | "Win"
    | "Meta"
    | "Shift";
type HotkeyType = { key: string; modifiers: HotkeyModifierType[] };

export const HotKeyUtil = {
    orderModifiers(modifiers: HotkeyModifierType[]) {
        const order = [
            "Control",
            "Ctrl",
            "Command",
            "Meta",
            "Win",
            "Option",
            "Alt",
            "Shift",
        ];
        return modifiers.sort((a, b) => {
            return order.indexOf(a) - order.indexOf(b);
        });
    },
    unifyObject(hotkey: HotkeyType) {
        return {
            key: hotkey.key.toUpperCase(),
            modifiers: this.orderModifiers(
                hotkey.modifiers.map((modifier) => StrUtil.ucFirst(modifier)),
            ),
        };
    },
    unifyString(hotkey: string): HotkeyType {
        const parts = hotkey.split("+");
        const key = parts.pop() || "";
        const modifiers: any[] = [];
        parts.forEach((part) => {
            modifiers.push(StrUtil.ucFirst(part.trim()));
        });
        return this.unifyObject({ key, modifiers });
    },
    unify(
        hotkeys: string | string[] | HotkeyType | HotkeyType[],
    ): HotkeyType[] {
        if (typeof hotkeys === "string") {
            return [this.unifyString(hotkeys)];
        } else if (Array.isArray(hotkeys)) {
            return hotkeys.map((hotkey) => {
                if (typeof hotkey === "string") {
                    return this.unifyString(hotkey);
                } else {
                    return this.unifyObject(hotkey);
                }
            });
        } else {
            return [this.unifyObject(hotkeys)];
        }
    },
    getFromEvent(event: any): HotkeyType | null {
        const valid = [
            "A",
            "B",
            "C",
            "D",
            "E",
            "F",
            "G",
            "H",
            "I",
            "J",
            "K",
            "L",
            "M",
            "N",
            "O",
            "P",
            "Q",
            "R",
            "S",
            "T",
            "U",
            "V",
            "W",
            "X",
            "Y",
            "Z",
            "1",
            "2",
            "3",
            "4",
            "5",
            "6",
            "7",
            "8",
            "9",
            "0",
            "Space",
        ];
        const key = (event.key || "").toUpperCase();
        if (!event || !event.key || !valid.includes(key)) {
            return null;
        }
        const modifiers: HotkeyModifierType[] = [];
        if (isWin) {
            if (event.ctrlKey || event.control) {
                modifiers.push("Ctrl");
            }
            if (event.altKey || event.alt) {
                modifiers.push("Alt");
            }
            if (event.metaKey || event.meta) {
                modifiers.push("Win");
            }
        } else if (isMac) {
            if (event.ctrlKey || event.control) {
                modifiers.push("Control");
            }
            if (event.altKey || event.alt) {
                modifiers.push("Option");
            }
            if (event.metaKey || event.meta) {
                modifiers.push("Command");
            }
        } else {
            if (event.ctrlKey || event.control) {
                modifiers.push("Ctrl");
            }
            if (event.altKey || event.alt) {
                modifiers.push("Alt");
            }
            if (event.metaKey || event.meta) {
                modifiers.push("Meta");
            }
        }
        if (event.shiftKey || event.shift) {
            modifiers.push("Shift");
        }
        return this.unifyObject({ key, modifiers });
    },
    match(hotkeysForMatch: HotkeyType[], hotkey: HotkeyType): boolean {
        if (!hotkeysForMatch || !hotkey) {
            return false;
        }
        const hotKeyStr = hotkey.modifiers.join("+") + "+" + hotkey.key;
        for (const key of hotkeysForMatch) {
            const keyStr = key.modifiers.join("+") + "+" + key.key;
            if (keyStr === hotKeyStr) {
                return true;
            }
        }
        return false;
    },
};


================================================
FILE: electron/main/fastPanel.ts
================================================
import { icnsLogoPath, icoLogoPath, logoPath } from "../config/icon";
import { AppRuntime } from "../mapi/env";
import { AppConfig } from "../../src/config";
import { isPackaged } from "../lib/env";
import { WindowConfig } from "../config/window";
import {
    preloadDefault,
    RENDERER_DIST,
    rendererLoadPath,
    VITE_DEV_SERVER_URL,
} from "../lib/env-main";
import * as remoteMain from "@electron/remote/main";
import { Page } from "../page";
import { BrowserWindow } from "electron";
import path from "node:path";
import { executeHooks } from "../mapi/manager/lib/hooks";
import { DevToolsManager } from "../lib/devtools";
import ConfigMain from "../mapi/config/main";

export const FastPanelMain = {
    init() {
        const fastPanelHtml = path.join(RENDERER_DIST, "page/fastPanel.html");
        let icon = logoPath;
        if (process.platform === "win32") {
            icon = icoLogoPath;
        } else if (process.platform === "darwin") {
            icon = icnsLogoPath;
        }
        AppRuntime.fastPanelWindow = new BrowserWindow({
            show: false,
            title: AppConfig.name,
            ...(!isPackaged ? { icon } : {}),
            frame: false,
            transparent: false,
            hasShadow: true,
            center: true,
            useContentSize: true,
            minWidth: WindowConfig.fastPanelWidth,
            minHeight: WindowConfig.fastPanelHeight,
            width: WindowConfig.fastPanelWidth,
            height: WindowConfig.fastPanelHeight,
            skipTaskbar: true,
            resizable: false,
            maximizable: false,
            backgroundColor: "#f1f5f9",
            alwaysOnTop: true,
            webPreferences: {
                preload: preloadDefault,
                // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
                nodeIntegration: true,
                contextIsolation: false,
                sandbox: false,
                webSecurity: false,
                webviewTag: true,
            },
        });

        AppRuntime.fastPanelWindow.on("closed", () => {
            AppRuntime.fastPanelWindow = null;
        });
        AppRuntime.fastPanelWindow.on("show", async () => {
            await executeHooks(AppRuntime.fastPanelWindow, "Show");
        });
        AppRuntime.fastPanelWindow.on("hide", async () => {
            await executeHooks(AppRuntime.fastPanelWindow, "Hide");
        });
        AppRuntime.fastPanelWindow.on("blur", async () => {
            const fastPanelAutoHide = await ConfigMain.getEnv(
                "fastPanelAutoHide",
                true,
            );
            if (fastPanelAutoHide) {
                AppRuntime.fastPanelWindow.hide();
            }
        });

        rendererLoadPath(AppRuntime.fastPanelWindow, "page/fastPanel.html");

        remoteMain.enable(AppRuntime.fastPanelWindow.webContents);

        AppRuntime.fastPanelWindow.webContents.on("did-finish-load", () => {
            Page.ready("fastPanel");
            DevToolsManager.autoShow(AppRuntime.fastPanelWindow);
        });
        DevToolsManager.register("FastPanel", AppRuntime.fastPanelWindow);
        // AppRuntime.fastPanelWindow.webContents.setWindowOpenHandler(({url}) => {
        //     if (url.startsWith('https:')) shell.openExternal(url)
        //     return {action: 'deny'}
        // })
    },
};


================================================
FILE: electron/main/index.ts
================================================
import { app, BrowserWindow, desktopCapturer, session, shell } from "electron";
import { optimizer } from "@electron-toolkit/utils";
import path from "node:path";
import fs from "node:fs";
/** process.js 必须位于非依赖项的顶部 */
import { isDummy } from "../lib/process";
import * as remoteMain from "@electron/remote/main";

import { AppEnv, AppRuntime } from "../mapi/env";
import { MAPI } from "../mapi/main";

import { WindowConfig } from "../config/window";
import { AppConfig } from "../../src/config";
import Log from "../mapi/log/main";
import { ConfigMenu } from "../config/menu";
import { ConfigLang } from "../config/lang";
import { ConfigContextMenu } from "../config/contextMenu";
import { preloadDefault, rendererLoadPath } from "../lib/env-main";
import { Page } from "../page";
import { ConfigTray } from "../config/tray";
import { icnsLogoPath, icoLogoPath, logoPath } from "../config/icon";
import { isMac, isPackaged } from "../lib/env";
import { FastPanelMain } from "./fastPanel";
import { executeHooks } from "../mapi/manager/lib/hooks";
import { AppPosition } from "../mapi/app/lib/position";
import { DevToolsManager } from "../lib/devtools";
import { reportError } from "../mapi/log/beacon";
import { AppsMain } from "../mapi/app/main";
import { ManagerEditor } from "../mapi/manager/editor";
import { ProtocolMain } from "../mapi/protocol/main";

app.commandLine.appendSwitch("enable-experimental-web-platform-features");

const isDummyNew = isDummy;

if (process.env["ELECTRON_ENV_PROD"]) {
    DevToolsManager.setEnable(false);
}

const logDebugContent = (label: string, content: any) => {
    const filePath = AppEnv.userData + "/debug.log";
    const msg = label + " - " + JSON.stringify(content);
    console.log(msg);
    fs.appendFileSync(filePath, msg + "\n");
};

process.on("uncaughtException", (reason) => {
    let error: any = reason;
    if (error instanceof Error) {
        error = [error.message, error.stack].join("\n");
    }
    Log.error("UncaughtException", error);
    reportError(
        reason instanceof Error ? reason.message : String(reason),
        reason instanceof Error ? reason.stack : undefined,
    );
});

process.on("unhandledRejection", (reason) => {
    let error: any = reason;
    if (error instanceof Error) {
        error = [error.message, error.stack].join("\n");
    }
    Log.error("UnhandledRejection", error);
    reportError(
        reason instanceof Error ? (reason as Error).message : String(reason),
        reason instanceof Error ? (reason as Error).stack : undefined,
    );
});

// Set application name for Windows 10+ notifications
if (process.platform === "win32") app.setAppUserModelId(app.getName());

if (!app.requestSingleInstanceLock()) {
    app.quit();
    process.exit(0);
}

// app.disableHardwareAcceleration();
// app.setAccessibilitySupportEnabled(true)

AppEnv.appRoot = process.env.APP_ROOT;
AppEnv.appData = app.getPath("appData");
AppEnv.userData = app.getPath("userData");
AppEnv.dataRoot = path.join(AppEnv.userData, "data");

if (!fs.existsSync(AppEnv.dataRoot)) {
    fs.mkdirSync(AppEnv.dataRoot, { recursive: true });
}
for (const dir of ["logs", "storage"]) {
    if (!fs.existsSync(path.join(AppEnv.dataRoot, dir))) {
        fs.mkdirSync(path.join(AppEnv.dataRoot, dir), { recursive: true });
    }
}

AppEnv.isInit = true;

MAPI.init();
ConfigContextMenu.init();

Log.info("Starting");
Log.info("LaunchInfo", {
    isPackaged,
    appRoot: AppEnv.appRoot,
    appData: AppEnv.appData,
    userData: AppEnv.userData,
    dataRoot: AppEnv.dataRoot,
});

async function createWindow() {
    let icon = logoPath;
    if (process.platform === "win32") {
        icon = icoLogoPath;
    } else if (process.platform === "darwin") {
        icon = icnsLogoPath;
    }
    const { x: wx, y: wy } = AppPosition.get(
        "main",
        (screenX, screenY, screenWidth, screenHeight) => {
            // console.log('calculator', {screenX, screenY, screenWidth, screenHeight});
            return {
                x: screenX + screenWidth / 2 - WindowConfig.mainWidth / 2,
                y: screenY + screenHeight / 8,
            };
        },
    );
    AppRuntime.mainWindow = new BrowserWindow({
        show: true,
        title: AppConfig.title,
        ...(!isPackaged ? { icon } : {}),
        frame: false,
        transparent: true,
        hasShadow: true,
        // center: true,
        x: wx,
        y: wy,
        useContentSize: true,
        minWidth: WindowConfig.mainWidth,
        minHeight: WindowConfig.mainHeight,
        width: WindowConfig.mainWidth,
        height: WindowConfig.mainHeight,
        skipTaskbar: true,
        resizable: false,
        maximizable: false,
        backgroundColor: await AppsMain.defaultDarkModeBackgroundColor(),
        webPreferences: {
            preload: preloadDefault,
            // Warning: Enable nodeIntegration and disable contextIsolation is not secure in production
            nodeIntegration: true,
            webSecurity: false,
            webviewTag: true,
            // Consider using contextBridge.exposeInMainWorld
            // Read more on https://www.electronjs.org/docs/latest/tutorial/context-isolation
            contextIsolation: false,
            // sandbox: false,
        },
    });

    AppRuntime.mainWindow.on("closed", () => {
        AppRuntime.mainWindow = null;
    });
    AppRuntime.mainWindow.on("show", async () => {
        await executeHooks(AppRuntime.mainWindow, "Show");
    });
    AppRuntime.mainWindow.on("hide", async () => {
        await executeHooks(AppRuntime.mainWindow, "Hide");
    });

    rendererLoadPath(AppRuntime.mainWindow, "index.html");

    remoteMain.enable(AppRuntime.mainWindow.webContents);
    AppRuntime.mainWindow.webContents.on("did-finish-load", () => {
        Page.ready("main");
        DevToolsManager.autoShow(AppRuntime.mainWindow);
    });
    AppRuntime.mainWindow.webContents.setWindowOpenHandler(({ url }) => {
        if (url.startsWith("https://") || url.startsWith("http://")) {
            shell.openExternal(url);
        }
        return { action: "deny" };
    });
    DevToolsManager.register("Main", AppRuntime.mainWindow);

    FastPanelMain.init();
}

const handleArgsForApp = (argv: string[]) => {
    let filePath = null;
    let url = null;
    for (let i = 1; i < argv.length; i++) {
        const arg = argv[i];
        if (arg.startsWith("--")) {
            continue;
        }
        if (["."].includes(arg)) {
            continue;
        }
        if (arg.startsWith("focusany://")) {
            url = arg;
            continue;
        }
        filePath = arg;
        break;
    }
    if (filePath) {
        ManagerEditor.openQueue(filePath).then();
    }
    if (url) {
        ProtocolMain.queue(url).then();
    }
};

app.on("open-file", (event, path) => {
    event.preventDefault();
    ManagerEditor.openQueue(path).then();
});

app.on("open-url", (event, url) => {
    event.preventDefault();
    ProtocolMain.queue(url).then();
});

app.whenReady()
    .then(() => {
        const isRegistered = app.setAsDefaultProtocolClient("focusany");
        Log.info("ProtocolRegistered", isRegistered);
        remoteMain.initialize();
        session.defaultSession.setDisplayMediaRequestHandler(
            (request, callback) => {
                desktopCapturer
                    .getSources({ types: ["screen"] })
                    .then((sources) => {
                        // Grant access to the first screen found.
                        callback({ video: sources[0], audio: "loopback" });
                    });
            },
        );
    })
    .then(ConfigLang.readyAsync)
    .then(() => {
        if (isMac) {
            app.dock.hide();
        }
        MAPI.ready();
        ConfigMenu.ready();
        ConfigTray.ready();
        app.on("browser-window-created", (_, window) => {
            optimizer.watchWindowShortcuts(window);
        });
        createWindow().then();
        handleArgsForApp(process.argv);
    });

app.on("before-quit", (event) => {
    if (!(app as any).forceQuit && isPackaged) {
        event.preventDefault();
    }
});

app.on("will-quit", () => {
    MAPI.destroy();
});

app.on("window-all-closed", () => {
    if (process.platform !== "darwin") app.quit();
});

app.on("second-instance", (event, argv) => {
    if (AppRuntime.mainWindow) {
        if (AppRuntime.mainWindow.isMinimized()) {
            AppRuntime.mainWindow.restore();
        }
        AppRuntime.mainWindow.show();
        AppRuntime.mainWindow.focus();
    }
    handleArgsForApp(argv);
});

app.on("activate", () => {
    const allWindows = BrowserWindow.getAllWindows();
    if (allWindows.length) {
        if (!AppRuntime.mainWindow.isVisible()) {
            AppRuntime.mainWindow.show();
        }
        AppRuntime.mainWindow.focus();
    } else {
        createWindow().then();
    }
});


================================================
FILE: electron/mapi/app/icons.ts
================================================
export const icons = {
    success:
        '<svg t="1733817409678" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1488" width="1024" height="1024"><path d="M512 832c-176.448 0-320-143.552-320-320S335.552 192 512 192s320 143.552 320 320-143.552 320-320 320m0-704C300.256 128 128 300.256 128 512s172.256 384 384 384 384-172.256 384-384S723.744 128 512 128" fill="#FFF" p-id="1489"></path><path d="M619.072 429.088l-151.744 165.888-62.112-69.6a32 32 0 1 0-47.744 42.624l85.696 96a32 32 0 0 0 23.68 10.688h0.192c8.96 0 17.536-3.776 23.616-10.4l175.648-192a32 32 0 0 0-47.232-43.2" fill="#FFF" p-id="1490"></path></svg>',
    error: '<svg t="1733817396560" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1326" width="1024" height="1024"><path d="M512 128C300.8 128 128 300.8 128 512s172.8 384 384 384 384-172.8 384-384S723.2 128 512 128zM512 832c-179.2 0-320-140.8-320-320s140.8-320 320-320 320 140.8 320 320S691.2 832 512 832z" fill="#FFF" p-id="1327"></path><path d="M672 352c-12.8-12.8-32-12.8-44.8 0L512 467.2 396.8 352C384 339.2 364.8 339.2 352 352S339.2 384 352 396.8L467.2 512 352 627.2c-12.8 12.8-12.8 32 0 44.8s32 12.8 44.8 0L512 556.8l115.2 115.2c12.8 12.8 32 12.8 44.8 0s12.8-32 0-44.8L556.8 512l115.2-115.2C684.8 384 684.8 364.8 672 352z" fill="#FFF" p-id="1328"></path></svg>',
    info: '<svg t="1733992721464" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1357" width="1024" height="1024"><path d="M512 898.71874973C299.30468777 898.71874973 125.28125027 724.69531223 125.28125027 512S299.30468777 125.28125027 512 125.28125027s386.71874973 174.0234375 386.71874973 386.71874973-174.0234375 386.71874973-386.71874973 386.71874973z m0-696.09375c-170.15625027 0-309.37500027 139.21875-309.37500027 309.37500027 0 170.15625027 139.21875 309.37500027 309.37500027 309.37500027 170.15625027 0 309.37500027-139.21875 309.37500027-309.37500027 0-170.15625027-139.21875-309.37500027-309.37500027-309.37500027z" fill="#FFFFFF" p-id="1358"></path><path d="M512 746.59765652a37.96875 37.96875 0 0 1-38.67187473-38.67187554v-221.6953125c0-21.9375 16.76953125-38.67187473 38.67187473-38.67187473 21.90234348 0 38.67187473 16.73437473 38.67187473 38.67187473v221.6953125c0 21.9375-16.76953125 38.67187473-38.67187473 38.67187554zM512 390.81640625a37.96875 37.96875 0 0 1-38.67187473-38.67187473V316.07421902c0-21.9375 16.76953125-38.67187473 38.67187473-38.67187554 21.90234348 0 38.67187473 16.73437473 38.67187473 38.67187554v36.0703125c0 21.9375-18.03515625 38.67187473-38.67187473 38.67187473z" fill="#FFFFFF" p-id="1359"></path></svg>',
    loading: `
<svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
    <rect  fill="#FFFFFF" x="21.5" y="21.5" width="25" height="25" rx="3" ry="3">
      <animate attributeName="x" calcMode="linear" values="21.5;53.5;53.5;53.5;53.5;21.5;21.5;21.5;21.5" keyTimes="0;0.083;0.25;0.333;0.5;0.583;0.75;0.833;1" dur="1.5" begin="-1.375s" repeatCount="indefinite"></animate>
      <animate attributeName="y" calcMode="linear" values="21.5;53.5;53.5;53.5;53.5;21.5;21.5;21.5;21.5" keyTimes="0;0.083;0.25;0.333;0.5;0.583;0.75;0.833;1" dur="1.5" begin="-1s" repeatCount="indefinite"></animate>
    </rect>
    <rect fill="#FFFFFF" x="21.5" y="53.5" width="25" height="25" rx="3" ry="3">
      <animate attributeName="x" calcMode="linear" values="21.5;53.5;53.5;53.5;53.5;21.5;21.5;21.5;21.5" keyTimes="0;0.083;0.25;0.333;0.5;0.583;0.75;0.833;1" dur="1.5" begin="-0.875s" repeatCount="indefinite"></animate>
      <animate attributeName="y" calcMode="linear" values="21.5;53.5;53.5;53.5;53.5;21.5;21.5;21.5;21.5" keyTimes="0;0.083;0.25;0.333;0.5;0.583;0.75;0.833;1" dur="1.5" begin="-0.5s" repeatCount="indefinite"></animate>
    </rect>
    <rect fill="#FFFFFF" x="53.5" y="42.919" width="25" height="25" rx="3" ry="3">
      <animate attributeName="x" calcMode="linear" values="21.5;53.5;53.5;53.5;53.5;21.5;21.5;21.5;21.5" keyTimes="0;0.083;0.25;0.333;0.5;0.583;0.75;0.833;1" dur="1.5" begin="-0.375s" repeatCount="indefinite"></animate>
      <animate attributeName="y" calcMode="linear" values="21.5;53.5;53.5;53.5;53.5;21.5;21.5;21.5;21.5" keyTimes="0;0.083;0.25;0.333;0.5;0.583;0.75;0.833;1" dur="1.5" begin="0s" repeatCount="indefinite"></animate>
    </rect>
  </svg>`,
};


================================================
FILE: electron/mapi/app/index.ts
================================================
import iconv from "iconv-lite";
import { exec as _exec, spawn } from "node:child_process";
import net from "node:net";
import util from "node:util";
import { AppConfig } from "../../../src/config";
import {
    extraResolveBin,
    isLinux,
    isMac,
    isWin,
    platformArch,
    platformName,
    platformUUID,
    platformVersion,
} from "../../lib/env";
import { IconvUtil, ShellUtil, StrUtil } from "../../lib/util";
import { Log } from "../log/index";

const exec = util.promisify(_exec);

const outputStringConvert = (outputEncoding: "utf8" | "cp936", data: any) => {
    if (!data) {
        return "";
    }
    if (outputEncoding === "utf8") {
        return data.toString();
    }
    let dataEncoding = "binary";
    if (Buffer.isBuffer(data)) {
        dataEncoding = IconvUtil.detect(data as any);
        if ("UTF-8" === dataEncoding) {
            return data.toString("utf8");
        }
    }
    // dataEncoding UTF-8 cp936
    // dataEncoding ISO-8859-1 cp936
    // console.log('dataEncoding', dataEncoding, outputEncoding)
    return iconv.decode(Buffer.from(data, dataEncoding as any), outputEncoding);
};

const shell = async (
    command: string,
    option?: {
        cwd?: string;
        outputEncoding?: string;
        shell?: boolean;
    },
) => {
    option = Object.assign(
        {
            cwd: process.cwd(),
            outputEncoding: isWin ? "cp936" : "utf8",
            shell: true,
        },
        option,
    );
    const result = await exec(command, {
        env: { ...process.env },
        shell: option.shell,
        encoding: "binary",
        cwd: option["cwd"],
    } as any);
    return {
        stdout: outputStringConvert(
            option.outputEncoding as any,
            result.stdout,
        ),
        stderr: outputStringConvert(
            option.outputEncoding as any,
            result.stderr,
        ),
    };
};

const spawnShell = async (
    command: string | string[],
    option: {
        stdout?: (data: string, process: any) => void;
        stderr?: (data: string, process: any) => void;
        success?: (process: any) => void;
        error?: (msg: string, exitCode: number, process: any) => void;
        cwd?: string;
        outputEncoding?: string;
        env?: Record<string, any>;
        shell?: boolean;
    } | null = null,
): Promise<{
    stop: () => void;
    send: (data: any) => void;
    result: () => Promise<string>;
}> => {
    option = Object.assign(
        {
            cwd: process.cwd(),
            outputEncoding: isWin ? "cp936" : "utf8",
            env: {},
            shell: true,
        },
        option,
    );
    let commandEntry = "",
        args = [];
    if (Array.isArray(command)) {
        commandEntry = command[0];
        args = command.slice(1);
    } else {
        args = ShellUtil.parseCommandArgs(command);
        commandEntry = args.shift() as string;
    }
    Log.info("App.spawnShell", {
        commandEntry,
        args,
        option: {
            cwd: option["cwd"],
            outputEncoding: option["outputEncoding"],
        },
    });
    const spawnProcess = spawn(commandEntry, args, {
        env: { ...process.env, ...option.env },
        cwd: option["cwd"],
        shell: option.shell,
        encoding: "binary",
    } as any);
    let end = false;
    let isSuccess = false;
    let exitCode = -1;
    const stdoutList: string[] = [];
    const stderrList: string[] = [];
    spawnProcess.stdout?.on("data", (data) => {
        // console.log('App.spawnShell.stdout', data)
        let dataString = outputStringConvert(
            option.outputEncoding as any,
            data,
        );
        Log.info("App.spawnShell.stdout", dataString);
        stdoutList.push(dataString);
        option.stdout?.(dataString, spawnProcess);
    });
    spawnProcess.stderr?.on("data", (data) => {
        // console.log('App.spawnShell.stderr', data)
        let dataString = outputStringConvert(
            option.outputEncoding as any,
            data,
        );
        Log.info("App.spawnShell.stderr", dataString);
        stderrList.push(dataString);
        option.stderr?.(dataString, spawnProcess);
    });
    spawnProcess.on("exit", (code, signal) => {
        // console.log('App.spawnShell.exit', code)
        Log.info("App.spawnShell.exit", { code, signal });
        exitCode = code;
        if (isWin) {
            if (0 === code || 1 === code) {
                isSuccess = true;
            }
        } else {
            if (null === code || 0 === code) {
                isSuccess = true;
            }
        }
        if (isSuccess) {
            option.success?.(spawnProcess);
        } else {
            option.error?.(
                `command ${command} failed with code ${code}`,
                exitCode,
                spawnProcess,
            );
        }
        end = true;
    });
    spawnProcess.on("error", (err) => {
        // console.log('App.spawnShell.error', err)
        Log.info("App.spawnShell.error", err);
        option.error?.(err.toString(), -1, spawnProcess);
        end = true;
    });
    return {
        stop: () => {
            Log.info("App.spawnShell.stop");
            if (isWin) {
                _exec(
                    `taskkill /pid ${spawnProcess.pid} /T /F`,
                    {
                        encoding: "binary",
                    },
                    (err, stdout, stderr) => {
                        if (stdout) {
                            stdout = outputStringConvert(
                                option.outputEncoding as any,
                                stdout,
                            );
                        }
                        if (stderr) {
                            stderr = outputStringConvert(
                                option.outputEncoding as any,
                                stderr,
                            );
                        }
                        Log.info(
                            "App.spawnShell.stop.taskkill",
                            JSON.parse(JSON.stringify({ err, stdout, stderr })),
                        );
                    },
                );
            } else {
                spawnProcess.kill("SIGINT");
            }
        },
        send: (data) => {
            Log.info("App.spawnShell.send", data);
            spawnProcess.stdin.write(data);
        },
        result: async (): Promise<string> => {
            if (end) {
                return stdoutList.join("") + stderrList.join("");
            }
            return new Promise((resolve, reject) => {
                const watchEnd = () => {
                    setTimeout(() => {
                        if (!end) {
                            watchEnd();
                            return;
                        }
                        if (isSuccess) {
                            resolve(stdoutList.join("") + stderrList.join(""));
                        } else {
                            reject(
                                [
                                    `command ${command} failed with code ${exitCode} : `,
                                    stdoutList.join(""),
                                    stderrList.join(""),
                                ].join(""),
                            );
                        }
                    }, 10);
                };
                watchEnd();
            });
        },
    };
};

const spawnBinary = async (
    binary: string,
    args: string[],
    option: {
        stdout?: (data: string, process: any) => void;
        stderr?: (data: string, process: any) => void;
        success?: (process: any) => void;
        error?: (msg: string, exitCode: number, process: any) => void;
        cwd?: string;
        outputEncoding?: string;
        env?: Record<string, any>;
        shell?: boolean;
    } | null = null,
): Promise<string> => {
    args.unshift(extraResolveBin(binary));
    const res = await Apps.spawnShell(args, {
        ...(option || {}),
        shell: false,
    });
    return await res.result();
};

const availablePortLock: {
    [port: number]: {
        lockKey: string;
        lockTime: number;
    };
} = {};

/**
 * 获取一个可用的端口
 * @param start 开始的端口
 * @param lockKey 锁定的key,避免其他进程获取,默认会创建一个随机的key
 * @param lockTime 锁定时间,避免在本次获取后未启动服务导致其他进程重复获取
 */
const availablePort = async (
    start: number,
    lockKey?: string,
    lockTime?: number,
): Promise<number> => {
    lockKey = lockKey || StrUtil.randomString(8);
    lockTime = lockTime || 60;
    // expire lock
    const now = Date.now();
    for (const port in availablePortLock) {
        const lockInfo = availablePortLock[port];
        if (lockInfo.lockTime < now) {
            delete availablePortLock[port];
        }
    }
    for (let i = start; i < 65535; i++) {
        const available = await isPortAvailable(i, "0.0.0.0");
        const availableLocal = await isPortAvailable(i, "127.0.0.1");
        // console.log('isPortAvailable', i, available, availableLocal)
        if (available && availableLocal) {
            const lockInfo = availablePortLock[i];
            if (lockInfo) {
                if (lockInfo.lockKey === lockKey) {
                    return i;
                } else {
                    // other lockKey lock the port
                    continue;
                }
            }
            availablePortLock[i] = {
                lockKey,
                lockTime: Date.now() + lockTime * 1000,
            };
            return i;
        }
    }
    throw new Error("no available port");
};

const isPortAvailable = async (
    port: number,
    host?: string,
): Promise<boolean> => {
    return new Promise((resolve) => {
        const server = net.createServer();
        server.listen(port, host);
        server.on("listening", () => {
            server.close();
            resolve(true);
        });
        server.on("error", () => {
            resolve(false);
        });
    });
};

const fixExecutable = async (executable: string) => {
    if (isMac || isLinux) {
        // chmod +x executable
        await shell(`chmod +x "${executable}"`);
    }
};

const getUserAgent = () => {
    let param = [];
    param.push(`AppOpen/${AppConfig.name}/${AppConfig.version}`);
    param.push(
        `Platform/${platformName()}/${platformArch()}/${platformVersion()}/${platformUUID()}`,
    );
    return param.join(" ");
};

export const Apps = {
    shell,
    spawnShell,
    spawnBinary,
    availablePort,
    isPortAvailable,
    fixExecutable,
    getUserAgent,
};

export default Apps;


================================================
FILE: electron/mapi/app/lib/position.ts
================================================
import { screen } from "electron";

type PositionCache = {
    x: 0;
    y: 0;
    screenWidth: 0;
    screenHeight: 0;
    id: -1;
};

export const AppPosition = {
    caches: {} as Record<string, PositionCache>,
    getCache(name: string): PositionCache {
        if (!this.caches[name]) {
            this.caches[name] = {
                x: 0,
                y: 0,
                screenWidth: 0,
                screenHeight: 0,
                id: -1,
            };
        }
        return this.caches[name];
    },
    get(
        name: string,
        calculator?: (
            screenX: number,
            screenY: number,
            screenWidth: number,
            screenHeight: number,
        ) => {
            x: number;
            y: number;
        },
    ): {
        x: number;
        y: number;
    } {
        const cache = this.getCache(name);
        const { x, y } = screen.getCursorScreenPoint();
        const currentDisplay = screen.getDisplayNearestPoint({ x, y });
        if (cache.id !== currentDisplay.id) {
            cache.id = currentDisplay.id;
            cache.screenWidth = currentDisplay.workArea.width;
            cache.screenHeight = currentDisplay.workArea.height;
            if (!calculator) {
                calculator = (
                    screenX: number,
                    screenY: number,
                    screenWidth: number,
                    screenHeight: number,
                ) => {
                    // console.log('calculator', {screenX, screenY, screenWidth, screenHeight});
                    return {
                        x: screenX + screenWidth / 10,
                        y: screenY + screenHeight / 10,
                    };
                };
            }
            const res = calculator(
                currentDisplay.workArea.x,
                currentDisplay.workArea.y,
                cache.screenWidth,
                cache.screenHeight,
            );
            cache.x = parseInt(String(res.x));
            cache.y = parseInt(String(res.y));
        }
        return {
            x: cache.x,
            y: cache.y,
        };
    },
    set(name: string, x: number, y: number): void {
        const cache = this.getCache(name);
        cache.x = x;
        cache.y = y;
    },
    getContextMenuPosition(
        boxWidth: number,
        boxHeight: number,
    ): {
        x: number;
        y: number;
    } {
        const { x, y } = screen.getCursorScreenPoint();
        const currentDisplay = screen.getDisplayNearestPoint({ x, y });
        let resultX = x;
        let resultY = y;
        if (currentDisplay.workArea.width - x < boxWidth) {
            resultX = currentDisplay.workArea.width - boxWidth;
        }
        if (currentDisplay.workArea.height - y < boxHeight) {
            resultY = currentDisplay.workArea.height - boxHeight;
        }
        return {
            x: resultX,
            y: resultY,
        };
    },
};


================================================
FILE: electron/mapi/app/loading.ts
================================================
import { BrowserWindow } from "electron";
import { AppsMain } from "./main";
import { icons } from "./icons";

export const makeLoading = (
    msg: string,
    options?: {
        timeout?: number;
        percentAuto?: boolean;
        percentTotalSeconds?: number;
    },
): {
    close: () => void;
    percent: (value: number) => void;
} => {
    options = Object.assign(
        {
            percentAuto: false,
            percentTotalSeconds: 30,
            timeout: 0,
        },
        options,
    );

    if (options.timeout === 0) {
        options.timeout = 60 * 10 * 1000;
    }
    // console.log('options', options)

    const display = AppsMain.getCurrentScreenDisplay();
    // console.log('xxxx', primaryDisplay);
    const width = display.workArea.width;
    const height = 60;
    const icon = icons.loading;

    const win = new BrowserWindow({
        height,
        width,
        x: 0,
        y: 0,
        modal: false,
        frame: false,
        alwaysOnTop: true,
        center: false,
        transparent: true,
        hasShadow: false,
        show: false,
        focusable: false,
        skipTaskbar: true,
    });
    const htmlContent = `
  <!DOCTYPE html>
  <html>
    <head>
      <style>
        html,body{
            height: 100vh;
            margin: 0;
            padding: 0;
            background: rgba(0, 0, 0, 0.4);
            color: #FFFFFF;
        }
        .message-view {
            height: 100vh;
            display:flex;
            text-align:center;
            padding:0 10px;
            position:relative;
        }
        .message-view #message{
            margin: auto;
            font-size: 16px;
            display: inline-block;
            line-height: 30px;
            white-space: nowrap;
        }
        .message-view #message .icon{
            width: 30px;
            height: 30px;
            display:inline-block;
            margin-right: 5px;
            vertical-align: top;
        }
        .message-view #percent{
            position: absolute;
            bottom: 5px;
            left: 5px;
            right: 5px;
            height: 5px;
            border-radius: 5px;
            background: rgba(255, 255, 255, 0.4);
            overflow: hidden;
            display:none;
        }
        .message-view #percent .value{
            border-radius: 5px;
            height: 100%;
            width: 0%;
            background: #FFFFFF;
        }
        ::-webkit-scrollbar {
          width: 0;
        }
      </style>
    </head>
    <body>
      <div class="message-view">
        <div id="message">${icon}${msg}</div>
        <div id="percent">
            <div class="value"></div>
        </div>
      </div>
    </body>
  </html>
`;

    const encodedHTML = encodeURIComponent(htmlContent);
    let percentAutoTimer = null;
    win.loadURL(`data:text/html;charset=UTF-8,${encodedHTML}`);
    win.on("ready-to-show", async () => {
        const width = Math.ceil(
            await win.webContents.executeJavaScript(`(()=>{
            const message = document.getElementById('message');
            const width = message.scrollWidth;
            return width;
        })()`),
        );
        win.setSize(width + 20, height);
        const x =
            display.workArea.x + display.workArea.width / 2 - (width + 20) / 2;
        const y = display.workArea.y + (display.workArea.height * 1) / 4;
        win.setPosition(Math.floor(x), Math.floor(y));
        win.show();
        if (options.percentAuto) {
            let percent = 0;
            percentAutoTimer = setInterval(
                () => {
                    percent += 0.01;
                    if (percent >= 1) {
                        clearInterval(percentAutoTimer);
                        return;
                    }
                    controller.percent(percent);
                },
                (options.percentTotalSeconds * 1000) / 100,
            );
        }
        // win.webContents.openDevTools({
        //     mode: 'detach'
        // })
    });
    const winCloseTimer = setTimeout(() => {
        win.close();
        clearTimeout(winCloseTimer);
    }, options.timeout);
    const controller = {
        close: () => {
            win.close();
            clearTimeout(winCloseTimer);
            if (percentAutoTimer) {
                clearInterval(percentAutoTimer);
            }
        },
        percent: (value: number) => {
            const percent = 100 * value;
            win.webContents.executeJavaScript(`(()=>{
                const percent = document.querySelector('#percent');
                const percentValue = document.querySelector('#percent .value');
                percent.style.display = 'block';
                percentValue.style.width = '${percent}%';
            })()`);
        },
    };
    return controller;
};


================================================
FILE: electron/mapi/app/main.ts
================================================
import {
    app,
    BrowserWindow,
    clipboard,
    ipcMain,
    nativeImage,
    nativeTheme,
    screen,
    shell,
} from "electron";
import { AppConfig } from "../../../src/config";
import { CommonConfig } from "../../config/common";
import { WindowConfig } from "../../config/window";
import {
    isDev,
    isMac,
    platformArch,
    platformName,
    platformUUID,
    platformVersion,
} from "../../lib/env";
import { preloadDefault, rendererDistPath } from "../../lib/env-main";
import { Page } from "../../page";
import { ConfigMain } from "../config/main";
import { AppRuntime } from "../env";
import { Events } from "../event/main";
import { Files } from "../file/main";
import Apps from "./index";
import { AppPosition } from "./lib/position";
import { makeLoading } from "./loading";
import { SetupMain } from "./setup";
import { makeToast } from "./toast";

const getWindowByName = (name?: string) => {
    if (!name || "main" === name) {
        return AppRuntime.mainWindow;
    }
    if ("fastPanel" === name) {
        return AppRuntime.fastPanelWindow;
    }
    return AppRuntime.windows[name];
};

const getCurrentWindow = (window, e) => {
    let originWindow = BrowserWindow.fromWebContents(e.sender);
    // if (originWindow !== window) originWindow = detachInstance.getWindow();
    return originWindow;
};

const quit = () => {
    app.quit();
};

ipcMain.handle("app:quit", () => {
    quit();
});

const restart = () => {
    app.relaunch();
};

ipcMain.handle("app:restart", () => {
    restart();
});

const windowMin = (name?: string) => {
    getWindowByName(name)?.minimize();
};

const windowMax = (name?: string) => {
    const win = getWindowByName(name);
    if (!win) {
        return;
    }
    if (win.isFullScreen()) {
        win.setFullScreen(false);
        win.unmaximize();
        win.center();
    } else if (win.isMaximized()) {
        win.unmaximize();
        win.center();
    } else {
        win.setMinimumSize(WindowConfig.minWidth, WindowConfig.minHeight);
        win.maximize();
    }
};

const windowSetSize = (
    name: string | null,
    width: number,
    height: number,
    option?: {
        includeMinimumSize: boolean;
        center: boolean;
    },
) => {
    width = parseInt(String(width));
    height = parseInt(String(height));
    // console.log('windowSetSize', name, width, height, option)
    const win = getWindowByName(name);
    if (!win) {
        return;
    }
    option = Object.assign(
        {
            includeMinimumSize: true,
            center: true,
        },
        option,
    );
    if (option.includeMinimumSize) {
        win.setMinimumSize(width, height);
    }
    win.setSize(width, height);
    if (option.center) {
        win.center();
    }
};

ipcMain.handle("app:openExternal", (event, url: string) => {
    return shell.openExternal(url);
});
ipcMain.handle("app:openPath", (event, url: string) => {
    return shell.openPath(url);
});
ipcMain.handle("app:showItemInFolder", (event, url: string) => {
    return shell.showItemInFolder(url);
});

ipcMain.handle("app:getPreload", (event) => {
    let preload = preloadDefault;
    if (!preload.startsWith("file://")) {
        preload = `file://${preload}`;
    }
    return preload;
});

ipcMain.handle("window:min", (event, name: string) => {
    windowMin(name);
});
ipcMain.handle("window:max", (event, name: string) => {
    windowMax(name);
});
ipcMain.handle(
    "window:setSize",
    (
        event,
        name: string | null,
        width: number,
        height: number,
        option?: {
            includeMinimumSize: boolean;
            center: boolean;
        },
    ) => {
        windowSetSize(name, width, height, option);
    },
);

ipcMain.handle("window:close", (event, name: string) => {
    getWindowByName(name)?.close();
});

const windowOpen = async (
    name: string,
    option?: {
        singleton?: boolean;
        parent?: BrowserWindow;
        [key: string]: any;
    },
) => {
    name = name || "main";
    return Page.open(name, option);
};

ipcMain.handle("window:open", (event, name: string, option: any) => {
    return windowOpen(name, option);
});

ipcMain.handle("window:hide", (event, name: string) => {
    getWindowByName(name)?.hide();
    if (isMac) {
        app.dock.hide();
    }
});

ipcMain.handle(
    "window:move",
    (
        event,
        name: string | null,
        data: {
            mouseX: number;
            mouseY: number;
            width: number;
            height: number;
        },
    ) => {
        const { x, y } = screen.getCursorScreenPoint();
        const originWindow = getWindowByName(name);
        if (!originWindow) return;
        originWindow.setBounds({
            x: x - data.mouseX,
            y: y - data.mouseY,
            width: data.width,
            height: data.height,
        });
        AppPosition.set(name, x - data.mouseX, y - data.mouseY);
    },
);

const getClipboardText = () => {
    return clipboard.readText("clipboard");
};

ipcMain.handle("app:getClipboardText", (event) => {
    return getClipboardText();
});

const setClipboardText = (text: string) => {
    clipboard.writeText(text, "clipboard");
};

ipcMain.handle("app:setClipboardText", (event, text: string) => {
    setClipboardText(text);
});

const getClipboardImage = () => {
    const image = clipboard.readImage("clipboard");
    return image.isEmpty() ? "" : image.toDataURL();
};

ipcMain.handle("app:getClipboardImage", (event) => {
    return getClipboardImage();
});

const setClipboardImage = (image: string) => {
    const img = nativeImage.createFromDataURL(image);
    clipboard.writeImage(img, "clipboard");
};

ipcMain.handle("app:setClipboardImage", (event, image: string) => {
    setClipboardImage(image);
});

const isDarkMode = () => {
    if (!CommonConfig.darkModeEnable) {
        return false;
    }
    return nativeTheme.shouldUseDarkColors;
};

const shouldDarkMode = async () => {
    if (!CommonConfig.darkModeEnable) {
        return false;
    }
    const darkMode = (await ConfigMain.get("darkMode")) || "auto";
    if ("dark" === darkMode) {
        return true;
    } else if ("light" === darkMode) {
        return false;
    } else if ("auto" === darkMode) {
        return isDarkMode();
    }
    return false;
};

const defaultDarkModeBackgroundColor = async () => {
    if (await shouldDarkMode()) {
        return "#17171A";
    }
    return "#00FFFFFF";
};

nativeTheme.on("updated", () => {
    Events.broadcast("DarkModeChange", { isDarkMode: isDarkMode() });
    AppsMain.defaultDarkModeBackgroundColor().then((color) => {
        AppRuntime.mainWindow.setBackgroundColor(color);
    });
});

ipcMain.handle("app:isDarkMode", () => {
    return isDarkMode();
});

const getCurrentScreenDisplay = () => {
    const screenPoint = screen.getCursorScreenPoint();
    const display = screen.getDisplayNearestPoint(screenPoint);
    return {
        bounds: display.bounds,
        workArea: display.workArea,
    };
};

const calcPositionInCurrentDisplay = (
    position:
        | "center"
        | "left-top"
        | "right-top"
        | "left-bottom"
        | "right-bottom",
    width: number,
    height: number,
) => {
    const { bounds, workArea } = getCurrentScreenDisplay();
    let x = 0;
    let y = 0;
    switch (position) {
        case "center":
            x = workArea.x + (workArea.width - width) / 2;
            y = workArea.y + (workArea.height - height) / 2;
            break;
        case "left-top":
            x = workArea.x;
            y = workArea.y;
            break;
        case "right-top":
            x = workArea.x + workArea.width - width;
            y = workArea.y;
            break;
        case "left-bottom":
            x = workArea.x;
            y = workArea.y + workArea.height - height;
            break;
        case "right-bottom":
            x = workArea.x + workArea.width - width;
            y = workArea.y + workArea.height - height;
            break;
    }
    return {
        x: Math.round(x),
        y: Math.round(y),
    };
};

const toast = (
    msg: string,
    options?: {
        duration?: number;
        status?: "success" | "error" | "info";
    },
) => {
    return makeToast(msg, options);
};

ipcMain.handle("app:toast", (event, msg: string, option?: any) => {
    return toast(msg, option);
});

const loading = (
    msg: string,
    options?: {
        timeout?: number;
        percentAuto?: boolean;
        percentTotalSeconds?: number;
    },
): {
    close: () => void;
    percent: (value: number) => void;
} => {
    return makeLoading(msg, options);
};

ipcMain.handle("app:loading", (event, msg: string, option?: any) => {
    return loading(msg, option);
});

ipcMain.handle("app:setupList", async () => {
    return SetupMain.list();
});

ipcMain.handle("app:setupOpen", async (event, name: string) => {
    return SetupMain.open(name);
});

const setupIsOk = async () => {
    return SetupMain.isOk();
};

ipcMain.handle("app:setupIsOk", async () => {
    return setupIsOk();
});

const getBuildInfo = async () => {
    if (isDev) {
        return {
            buildId: "Development",
        };
    }
    const json = await Files.read(rendererDistPath("build.json"), {
        isDataPath: false,
    });
    return JSON.parse(json);
};

ipcMain.handle("app:getBuildInfo", async () => {
    return getBuildInfo();
});

const collect = async (options?: {}) => {
    return {
        userAgent: Apps.getUserAgent(),
        name: AppConfig.name,
        version: AppConfig.version,
        uuid: platformUUID(),
        platformVersion: platformVersion(),
        platformName: platformName(),
        platformArch: platformArch(),
    };
};

ipcMain.handle("app:collect", async (event, options?: {}) => {
    return collect(options);
});

const setAutoLaunch = async (enable: boolean, options?: {}) => {
    return app.setLoginItemSettings({
        openAtLogin: enable,
    });
};

ipcMain.handle(
    "app:setAutoLaunch",
    async (event, enable: boolean, options?: {}) => {
        return setAutoLaunch(enable, options);
    },
);

const getAutoLaunch = async (options?: {}) => {
    return app.getLoginItemSettings().openAtLogin;
};

ipcMain.handle("app:getAutoLaunch", async (event, options?: {}) => {
    return getAutoLaunch(options);
});

export default {
    quit,
};

export const AppsMain = {
    shouldDarkMode,
    defaultDarkModeBackgroundColor,
    getWindowByName,
    getClipboardText,
    setClipboardText,
    getClipboardImage,
    setClipboardImage,
    getCurrentScreenDisplay,
    calcPositionInCurrentDisplay,
    toast,
    loading,
    setupIsOk,
    windowOpen,
};


================================================
FILE: electron/mapi/app/render.ts
================================================
import { ipcRenderer } from "electron";
import { resolve } from "node:path";
import { isPackaged, platformArch, platformName } from "../../lib/env";
import { AppEnv, waitAppEnvReady } from "../env";
import appIndex from "./index";

const isDarkMode = async () => {
    return ipcRenderer.invoke("app:isDarkMode");
};

const quit = () => {
    return ipcRenderer.invoke("app:quit");
};

const restart = () => {
    return ipcRenderer.invoke("app:restart");
};

const isPlatform = (name: "win" | "osx" | "linux") => {
    return platformName() === name;
};

const windowMin = (name?: string) => {
    return ipcRenderer.invoke("window:min", name);
};

const windowMax = (name?: string) => {
    return ipcRenderer.invoke("window:max", name);
};

const windowSetSize = (
    name: string | null,
    width: number,
    height: number,
    option?: {
        includeMinimumSize: boolean;
        center: boolean;
    },
) => {
    return ipcRenderer.invoke("window:setSize", name, width, height, option);
};

const windowOpen = (name: string, option: any) => {
    return ipcRenderer.invoke("window:open", name, option);
};

const windowHide = (name: string) => {
    return ipcRenderer.invoke("window:hide", name);
};

const windowClose = (name: string) => {
    return ipcRenderer.invoke("window:close", name);
};

const windowMove = (
    name: string | null,
    data: { mouseX: number; mouseY: number; width: number; height: number },
) => {
    return ipcRenderer.invoke("window:move", name, data);
};

const openExternal = (url: string) => {
    return ipcRenderer.invoke("app:openExternal", url);
};

const openPath = (url: string) => {
    return ipcRenderer.invoke("app:openPath", url);
};

const showItemInFolder = (url: string) => {
    return ipcRenderer.invoke("app:showItemInFolder", url);
};

const getPreload = async () => {
    return ipcRenderer.invoke("app:getPreload");
};

const resourcePathResolve = async (filePath: string) => {
    await waitAppEnvReady();
    const basePath = isPackaged ? process.resourcesPath : AppEnv.appRoot;
    return resolve(basePath, filePath);
};

const extraPathResolve = async (filePath: string) => {
    await waitAppEnvReady();
    const basePath = isPackaged ? process.resourcesPath : "electron/resources";
    return resolve(basePath, "extra", filePath);
};

const appEnv = async () => {
    await waitAppEnvReady();
    return AppEnv;
};

const setRenderAppEnv = (env: any) => {
    AppEnv.isInit = true;
    AppEnv.appRoot = env.appRoot;
    AppEnv.appData = env.appData;
    AppEnv.userData = env.userData;
    AppEnv.dataRoot = env.dataRoot;
};

const getClipboardText = () => {
    return ipcRenderer.invoke("app:getClipboardText");
};

const setClipboardText = (text: string) => {
    return ipcRenderer.invoke("app:setClipboardText", text);
};

const getClipboardImage = () => {
    return ipcRenderer.invoke("app:getClipboardImage");
};

const setClipboardImage = (image: string) => {
    return ipcRenderer.invoke("app:setClipboardImage", image);
};

const toast = (msg: string, option?: any) => {
    return ipcRenderer.invoke("app:toast", msg, option);
};

const setupList = () => {
    return ipcRenderer.invoke("app:setupList");
};

const setupOpen = (name: string) => {
    return ipcRenderer.invoke("app:setupOpen", name);
};

const setupIsOk = async () => {
    return ipcRenderer.invoke("app:setupIsOk");
};

const getBuildInfo = async () => {
    return ipcRenderer.invoke("app:getBuildInfo");
};

const collect = async (options?: {}) => {
    return ipcRenderer.invoke("app:collect", options);
};

const setAutoLaunch = async (enable: boolean, options?: {}) => {
    return ipcRenderer.invoke("app:setAutoLaunch", enable, options);
};

const getAutoLaunch = async (options?: {}) => {
    return ipcRenderer.invoke("app:getAutoLaunch", options);
};

export const AppsRender = {
    isDarkMode,
    resourcePathResolve,
    extraPathResolve,
    platformName,
    platformArch,
    isPlatform,
    quit,
    restart,
    windowMin,
    windowMax,
    windowSetSize,
    windowOpen,
    windowHide,
    windowClose,
    windowMove,
    openExternal,
    openPath,
    showItemInFolder,
    getPreload,
    appEnv,
    setRenderAppEnv,
    getClipboardText,
    setClipboardText,
    getClipboardImage,
    setClipboardImage,
    toast,
    setupList,
    setupOpen,
    setupIsOk,
    getBuildInfo,
    collect,
    setAutoLaunch,
    getAutoLaunch,
    shell: appIndex.shell,
    spawnShell: appIndex.spawnShell,
    spawnBinary: appIndex.spawnBinary,
    availablePort: appIndex.availablePort,
    fixExecutable: appIndex.fixExecutable,
    getUserAgent: appIndex.getUserAgent,
};

export default AppsRender;


================================================
FILE: electron/mapi/app/setup.ts
================================================
import { Permissions } from "../../lib/permission";
import { rendererDistPath } from "../../lib/env-main";

export const SetupMain = {
    async isOk() {
        if (!(await Permissions.checkAccessibilityAccess())) {
            return false;
        }
        if (!(await Permissions.checkScreenCaptureAccess())) {
            return false;
        }
        return true;
    },
    async list() {
        return [
            {
                name: "accessibility",
                title: t("setup.accessibility.title"),
                status: (await Permissions.checkAccessibilityAccess())
                    ? "success"
                    : "fail",
                desc: t("setup.accessibility.desc"),
                steps: [
                    {
                        title: t("setup.accessibility.step"),
                        image: rendererDistPath("setup/accessibility.png"),
                    },
                ],
            },
            {
                name: "screen",
                title: t("setup.screen.title"),
                status: (await Permissions.checkScreenCaptureAccess())
                    ? "success"
                    : "fail",
                desc: t("setup.screen.desc"),
                steps: [
                    {
                        title: t("setup.screen.step"),
                        image: rendererDistPath("setup/screen.png"),
                    },
                ],
            },
        ];
    },
    async open(name: string) {
        switch (name) {
            case "accessibility":
                Permissions.askAccessibilityAccess().then();
                break;
            case "screen":
                Permissions.askScreenCaptureAccess().then();
                break;
        }
    },
};


================================================
FILE: electron/mapi/app/toast.ts
================================================
import { BrowserWindow } from "electron";
import { icons } from "./icons";
import { AppsMain } from "./main";

let win = null;
let winCloseTimer = null;
let winShowTime = null;
const toastMsgQueue: { msg: string; options: any }[] = [];

export const makeToast = async (
    msg: string,
    options?: {
        duration?: number;
        status?: "success" | "error" | "info";
    },
) => {
    if (win) {
        if (winShowTime && Date.now() - winShowTime < 1000) {
            // make previous toast last at least 1 second
            if (toastMsgQueue.length > 0) {
                toastMsgQueue.forEach((item) => {
                    item.options = Object.assign({}, item.options, {
                        duration: 1000,
                    });
                });
            }
            toastMsgQueue.push({ msg, options });
            await new Promise((resolve) =>
                setTimeout(resolve, 1000 - (Date.now() - winShowTime)),
            );
            if (win) {
                win.close();
            }
            return;
        }
        win.close();
    }
    winShowTime = Date.now();

    options = Object.assign(
        {
            status: "info",
            duration: 0,
        },
        options,
    );

    if (options.duration === 0) {
        options.duration = Math.max(msg.length * 400, 3000);
    }
    // console.log('toast', msg, options)

    const display = AppsMain.getCurrentScreenDisplay();
    // console.log('xxxx', primaryDisplay);
    const width = display.workArea.width;
    const height = 60;
    const icon = icons[options.status] || icons.success;

    win = new BrowserWindow({
        height,
        width,
        parent: null,
        x: 0,
        y: 0,
        modal: false,
        frame: false,
        alwaysOnTop: true,
        // opacity: 0.9,
        center: false,
        transparent: true,
        hasShadow: false,
        show: false,
        focusable: false,
        skipTaskbar: true,
    });
    const htmlContent = `
    <!DOCTYPE html>
    <html>
        <head>
            <style>
                html,body{
                        height: 100%;
                        margin: 0;
                        padding: 0;
                        background: transparent;
                        color: #FFFFFF;
                        font-family: "PingFang SC", "Helvetica Neue", Helvetica, STHeiTi, "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
                }
                .message-view {
                        height: 100%;
                        text-align:center;
                        box-sizing: border-box;
                        background-color:transparent;
                        padding: 10px;
                }
                .message-view div{
                        margin: auto;
                        font-size: 16px;
                        display: inline-flex;
                        align-items: center;
                        line-height: 20px;
                        background: rgba(0, 0, 0, 0.85);
                        border-radius: 15px;
                        padding: 10px 10px;
                        box-shadow: 5px 5px 5px rgba(0,0,0,0.3);
                }
                .message-view div .icon{
                        width: 30px;
                        height: 30px;
                        display:inline-block;
                        margin-right: 8px;
                        vertical-align: middle;
                        flex-shrink: 0;
                }
                ::-webkit-scrollbar {
                    width: 0;
                }
            </style>
        </head>
        <body>
            <div class="message-view" onclick="window.close()">
                <div id="message">${icon}${msg}</div>
            </div>
        </body>
    </html>
`;

    const encodedHTML = encodeURIComponent(htmlContent);
    win.loadURL(`data:text/html;charset=UTF-8,${encodedHTML}`);
    win.on("ready-to-show", async () => {
        if (!win) return;
        const containerSize = await win.webContents.executeJavaScript(`(()=>{
            const message = document.getElementById('message');
            const width = message.scrollWidth;
            const height = message.scrollHeight;
            return {width:width,height:height};
        })()`);
        // console.log('containerSize', containerSize);
        const containerWidth = containerSize.width + 20;
        const containerHeight = containerSize.height + 20;
        win.setSize(containerWidth, containerHeight);
        const x =
            display.workArea.x +
            display.workArea.width / 2 -
            containerWidth / 2;
        const y = display.workArea.y + (display.workArea.height * 1) / 4;
        win.setPosition(Math.floor(x), Math.floor(y));
        win.showInactive();
        // win.webContents.openDevTools({
        //     mode: 'detach'
        // })
    });
    win.on("closed", () => {
        win = null;
        if (winCloseTimer) {
            clearTimeout(winCloseTimer);
        }
        setTimeout(() => {
            if (toastMsgQueue.length > 0) {
                const item = toastMsgQueue.shift();
                makeToast(item.msg, item.options);
            }
        }, 0);
    });
    winCloseTimer = setTimeout(() => {
        winCloseTimer = null;
        if (!win) return;
        win.close();
    }, options.duration);
};


================================================
FILE: electron/mapi/config/index.ts
================================================
import { callHandleFromMainOrRender } from "../env";

const all = async () => {
    return callHandleFromMainOrRender("config:all");
};

const get = async (key: string, defaultValue: any = null) => {
    return callHandleFromMainOrRender("config:get", key, defaultValue);
};
const set = async (key: string, value: any) => {
    await callHandleFromMainOrRender("config:set", key, value);
};

const allEnv = async () => {
    return callHandleFromMainOrRender("config:allEnv");
};

const getEnv = async (key: string, defaultValue: any = null) => {
    return callHandleFromMainOrRender("config:getEnv", key, defaultValue);
};

const setEnv = async (key: string, value: any) => {
    await callHandleFromMainOrRender("config:setEnv", key, value);
};

export const ConfigIndex = {
    all,
    get,
    set,
    allEnv,
    getEnv,
    setEnv,
};


================================================
FILE: electron/mapi/config/main.ts
================================================
import path from "node:path";
import { AppEnv } from "../env";
import fs from "node:fs";
import { ipcMain } from "electron";
import { Events } from "../event/main";

let data = null;
let dataEnv = {};

const userDataRoot = () => {
    return path.join(AppEnv.userData, "config.json");
};

const dataRoot = () => {
    return path.join(AppEnv.dataRoot, "config.json");
};

const filePath = () => {
    if (fs.existsSync(userDataRoot())) {
        return userDataRoot();
    }
    return dataRoot();
};

const load = () => {
    try {
        let json = fs.readFileSync(filePath()).toString();
        json = JSON.parse(json);
        data = json || {};
    } catch (e) {
        data = {};
    }
};

const loadIfNeed = () => {
    if (data === null) {
        load();
    }
};

const save = () => {
    fs.writeFileSync(filePath(), JSON.stringify(data, null, 4));
};

const all = async () => {
    loadIfNeed();
    return data;
};

const get = async (key: string, defaultValue: any = null) => {
    loadIfNeed();
    if (!(key in data)) {
        data[key] = defaultValue;
        save();
    }
    return data[key];
};

const set = async (key: string, value: any) => {
    loadIfNeed();
    data[key] = value;
    save();
};

const allEnv = async () => {
    return dataEnv;
};

const getEnv = async (key: string, defaultValue: any = null) => {
    if (!(key in dataEnv)) {
        dataEnv[key] = defaultValue;
    }
    return dataEnv[key];
};

const setEnv = async (key: string, value: any) => {
    dataEnv[key] = value;
};

ipcMain.handle("config:all", async (_) => {
    return await all();
});
ipcMain.handle(
    "config:get",
    async (_, key: string, defaultValue: any = null) => {
        return await get(key, defaultValue);
    },
);
ipcMain.handle("config:set", async (_, key: string, value: any) => {
    const res = await set(key, value);
    Events.broadcast("ConfigChange", { key, value });
    return res;
});

ipcMain.handle("config:allEnv", async (_) => {
    return await allEnv();
});

ipcMain.handle(
    "config:getEnv",
    async (_, key: string, defaultValue: any = null) => {
        return await getEnv(key, defaultValue);
    },
);

ipcMain.handle("config:setEnv", async (_, key: string, value: any) => {
    const res = await setEnv(key, value);
    Events.broadcast("ConfigEnvChange", { key, value });
    return res;
});

export const ConfigMain = {
    all,
    get,
    set,
    allEnv,
    getEnv,
    setEnv,
};

export default ConfigMain;


================================================
FILE: electron/mapi/config/render.ts
================================================
import { ipcRenderer } from "electron";

const all = async () => {
    return ipcRenderer.invoke("config:all");
};

const get = async (key: string, defaultValue: any = null) => {
    return ipcRenderer.invoke("config:get", key, defaultValue);
};

const set = async (key: string, value: any) => {
    return ipcRenderer.invoke("config:set", key, value);
};

const allEnv = async () => {
    return ipcRenderer.invoke("config:allEnv");
};

const getEnv = async (key: string, defaultValue: any = null) => {
    return ipcRenderer.invoke("config:getEnv", key, defaultValue);
};

const setEnv = async (key: string, value: any) => {
    return ipcRenderer.invoke("config:setEnv", key, value);
};

export default {
    all,
    get,
    set,
    allEnv,
    getEnv,
    setEnv,
};


================================================
FILE: electron/mapi/db/db.ts
================================================


================================================
FILE: electron/mapi/db/main.ts
================================================
import sqlite3, { Database } from "better-sqlite3";
import path from "node:path";
import migration from "./migration";
import { AppEnv } from "../env";
import { Log } from "../log/main";
import { ipcMain } from "electron";
import fs from "node:fs";
import { Files } from "../file/main";

let dbPath: string | null = null;
let dbConn: Database | null = null;
let dbSuccess = false;

const db = {
    /**
     * 检查数据库连接是否已初始化
     * @throws {string} 如果数据库未初始化则抛出异常
     */
    _check() {
        if (!dbSuccess) {
            throw "DBNotInitialized";
        }
    },
    /**
     * 执行SQL语句(无返回值)
     * @param {string} sql - SQL语句
     * @param {any[]} params - 参数数组
     * @returns {Promise<void>}
     */
    async execute(sql: string, params: any = []): Promise<void> {
        db._check();
        try {
            dbConn.prepare(sql).run(...params);
        } catch (err) {
            throw err;
        }
    },
    /**
     * 插入数据并返回插入的行ID
     * @param {string} sql - SQL语句
     * @param {any[]} params - 参数数组
     * @returns {Promise<string | number>} 插入的行ID
     */
    async insert(sql: string, params: any = []): Promise<string | number> {
        db._check();
        try {
            const result = dbConn.prepare(sql).run(...params);
            return result.lastInsertRowid;
        } catch (err) {
            throw err;
        }
    },
    /**
     * 查询单行数据
     * @param {string} sql - SQL语句
     * @param {any[]} params - 参数数组
     * @returns {Promise<any>} 查询结果
     */
    async first(sql: string, params: any = []): Promise<any> {
        db._check();
        try {
            return dbConn.prepare(sql).get(...params);
        } catch (err) {
            throw err;
        }
    },
    /**
     * 查询多行数据
     * @param {string} sql - SQL语句
     * @param {any[]} params - 参数数组
     * @returns {Promise<any[]>} 查询结果数组
     */
    async select(sql: string, params: any = []): Promise<any[]> {
        db._check();
        try {
            return dbConn.prepare(sql).all(...params);
        } catch (err) {
            throw err;
        }
    },
    /**
     * 更新数据并返回影响的行数
     * @param {string} sql - SQL语句
     * @param {any[]} params - 参数数组
     * @returns {Promise<number>} 影响的行数
     */
    async update(sql: string, params: any = []): Promise<number> {
        db._check();
        try {
            const result = dbConn.prepare(sql).run(...params);
            return result.changes;
        } catch (err) {
            throw err;
        }
    },
    /**
     * 删除数据并返回影响的行数
     * @param {string} sql - SQL语句
     * @param {any[]} params - 参数数组
     * @returns {Promise<number>} 影响的行数
     */
    async delete(sql: string, params: any = []): Promise<number> {
        db._check();
        try {
            const result = dbConn.prepare(sql).run(...params);
            return result.changes;
        } catch (err) {
            throw err;
        }
    },
};

const migrate = async () => {
    await db.execute(`CREATE TABLE IF NOT EXISTS migrate
                      (
                          id
                          INTEGER
                          PRIMARY
                          KEY,
                          version
                          INTEGER
                      )`);
    for (const version of migration.versions) {
        const result = await db.first(
            `SELECT *
             FROM migrate
             WHERE version = ?`,
            [version.version],
        );
        if (!result) {
            Log.info(`DB.Migrate`, { version: version.version });
            await version.up(db);
            await db.execute(
                `INSERT INTO migrate (version)
                 VALUES (?)`,
                [version.version],
            );
        }
    }
};

/**
 * 初始化数据库连接
 * @returns {Promise<void>}
 */
const init = async () => {
    dbPath = path.join(AppEnv.dataRoot, "database.db");
    const userDbPath = path.join(AppEnv.userData, "database.db");
    if (fs.existsSync(userDbPath)) {
        dbPath = userDbPath;
    }
    try {
        dbConn = new sqlite3(dbPath);
        dbSuccess = true;
        await migrate();
        Log.info("Database connected successfully");
    } catch (err) {
        Log.error("DBConnect SQLite database failed:", err.message);
        throw err;
    }
};

ipcMain.handle("db:execute", (event, sql: string, params: any) => {
    return db.execute(sql, params);
});
ipcMain.handle("db:insert", (event, sql: string, params: any) => {
    return db.insert(sql, params);
});
ipcMain.handle("db:first", (event, sql: string, params: any) => {
    return db.first(sql, params);
});
ipcMain.handle("db:select", (event, sql: string, params: any) => {
    return db.select(sql, params);
});
ipcMain.handle("db:update", (event, sql: string, params: any) => {
    return db.update(sql, params);
});
ipcMain.handle("db:delete", (event, sql: string, params: any) => {
    return db.delete(sql, params);
});

export const DBMain = {
    init,
    execute: db.execute,
    insert: db.insert,
    first: db.first,
    select: db.select,
    update: db.update,
    delete: db.delete,
};

export default DBMain;


================================================
FILE: electron/mapi/db/migration.ts
================================================
const versions = [
    {
        version: 0,
        up: async (db: DB) => {
            // await db.execute(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)`);
            // console.log('db.insert', await db.insert(`INSERT INTO users (name, email) VALUES (?, ?)`,['Alice', 'alice@example.com']));
            // console.log('db.select', await db.select(`SELECT * FROM users`));
            // console.log('db.first', await db.first(`SELECT * FROM users`));
        },
    },
    {
        version: 1,
        up: async (db: DB) => {
            await db.execute(`CREATE TABLE IF NOT EXISTS kvdb_data
                              (
                                  id           TEXT PRIMARY KEY,
                                  cloudVersion INTEGER,
                                  version      INTEGER,
                                  isDeleted    INTEGER,
                                  name         TEXT
                              )`);
            await db.execute(`CREATE INDEX IF NOT EXISTS idx_kvdb_data_name
                ON kvdb_data (name)
            `);
        },
    },
];

export default {
    versions,
};


================================================
FILE: electron/mapi/db/render.ts
================================================
import { ipcRenderer } from "electron";

const init = () => {};

const execute = async (sql: string, params: any = []) => {
    return ipcRenderer.invoke("db:execute", sql, params);
};

const insert = async (sql: string, params: any = []) => {
    return ipcRenderer.invoke("db:insert", sql, params);
};

const first = async (sql: string, params: any = []) => {
    return ipcRenderer.invoke("db:first", sql, params);
};

const select = async (sql: string, params: any = []) => {
    return ipcRenderer.invoke("db:select", sql, params);
};

const update = async (sql: string, params: any = []) => {
    return ipcRenderer.invoke("db:update", sql, params);
};

const deletes = async (sql: string, params: any = []) => {
    return ipcRenderer.invoke("db:delete", sql, params);
};

export default {
    init,
    execute,
    insert,
    first,
    select,
    update,
    delete: deletes,
};


================================================
FILE: electron/mapi/db/type.d.ts
================================================
type DB = {
    execute(sql: string, params?: any): Promise<any>;
    insert(sql: string, params?: any): Promise<any>;
    first(sql: string, params?: any): Promise<any>;
    select(sql: string, params?: any): Promise<any>;
    update(sql: string, params?: any): Promise<any>;
    delete(sql: string, params?: any): Promise<any>;
};


================================================
FILE: electron/mapi/env.ts
================================================
import electron, { BrowserWindow } from "electron";
import { Log } from "./log";

export const AppEnv = {
    isInit: false,
    appRoot: null as string,
    appData: null as string,
    userData: null as string,
    dataRoot: null as string,
};

export const AppRuntime = {
    fileHubRoot: null as string,
    splashWindow: null as BrowserWindow,
    mainWindow: null as BrowserWindow,
    fastPanelWindow: null as BrowserWindow,
    windows: {} as Record<string, BrowserWindow>,
};

export const waitAppEnvReady = async () => {
    while (!AppEnv.isInit) {
        await new Promise((resolve) => {
            setTimeout(resolve, 1000);
        });
    }
};

export const callHandleFromMainOrRender = async (name: string, ...args) => {
    if (electron.ipcRenderer) {
        return electron.ipcRenderer.invoke(name, ...args);
    } else {
        // @ts-ignore
        const func = electron.ipcMain._invokeHandlers.get(name);
        if (func) {
            return func(...args);
        } else {
            Log.error(`No handler found for ${name}`);
            return null;
        }
    }
};


================================================
FILE: electron/mapi/event/main.ts
================================================
import { AppRuntime } from "../env";
import { ipcMain, WebContents } from "electron";
import { StrUtil } from "../../lib/util";
import { ManagerWindow } from "../manager/window";

const init = async () => {};

type NameType = "main" | "fastPanel" | string | WebContents;
type EventType = "APP_READY" | "CALL_PAGE" | "CHANNEL" | "BROADCAST";
type BroadcastType =
    | "ConfigChange"
    | "ConfigEnvChange"
    | "UserChange"
    | "DarkModeChange"
    | "HotkeyWatch"
    | "Notice"
    | "MonitorEvent";

const broadcast = (
    type: BroadcastType,
    data: any,
    option?: {
        limit?: boolean;
        scopes?: string[];
        pages?: string[];
    },
) => {
    data = data || {};
    option = Object.assign(
        {
            limit: false,
            scopes: [],
            pages: [],
        },
        option,
    );
    if (option.pages.length > 0) {
        for (const p of option.pages) {
            send(p, "BROADCAST", { type, data });
        }
    } else {
        if (!option.limit || option.scopes.includes("main")) {
            send("main", "BROADCAST", { type, data });
        }
        if (!option.limit || option.scopes.includes("pages")) {
            for (let name in AppRuntime.windows) {
                send(name, "BROADCAST", { type, data });
            }
        }
    }
    if (!option.limit || option.scopes.includes("fastPanel")) {
        send("fastPanel", "BROADCAST", { type, data });
    }
    if (!option.limit || option.scopes.includes("views")) {
        for (const view of ManagerWindow.listBrowserViews()) {
            view.webContents.send("MAIN_PROCESS_MESSAGE", {
                id: StrUtil.randomString(32),
                type: "BROADCAST",
                data: { type, data },
            });
        }
    }
    if (!option.limit || option.scopes.includes("detachWindows")) {
        for (const win of ManagerWindow.listDetachWindows()) {
            win.webContents.send("MAIN_PROCESS_MESSAGE", {
                id: StrUtil.randomString(32),
                type: "BROADCAST",
                data: { type, data },
            });
        }
    }
};

const sendRaw = (
    webContents: any,
    type: EventType,
    data: any = {},
    id?: string,
): boolean => {
    id = id || StrUtil.randomString(32);
    const payload = { id, type, data };
    webContents.send("MAIN_PROCESS_MESSAGE", payload);
    return true;
};

const send = (
    name: NameType,
    type: EventType,
    data: any = {},
    id?: string,
): boolean => {
    id = id || StrUtil.randomString(32);
    const payload = { id, type, data };
    if (typeof name !== "string") {
        (name as WebContents).send("MAIN_PROCESS_MESSAGE", payload);
        return true;
    }
    if (name === "main") {
        if (!AppRuntime.mainWindow) {
            return false;
        }
        // console.log('send', payload)
        AppRuntime.mainWindow?.webContents.send(
            "MAIN_PROCESS_MESSAGE",
            payload,
        );
    } else if (name === "fastPanel") {
        if (!AppRuntime.fastPanelWindow) {
            return false;
        }
        AppRuntime.fastPanelWindow?.webContents.send(
            "MAIN_PROCESS_MESSAGE",
            payload,
        );
    } else {
        if (!AppRuntime.windows[name]) {
            return false;
        }
        AppRuntime.windows[name]?.webContents.send(
            "MAIN_PROCESS_MESSAGE",
            payload,
        );
    }
    return true;
};

ipcMain.handle(
    "event:send",
    async (_, name: NameType, type: EventType, data: any) => {
        send(name, type, data);
    },
);

const callPage = async (
    name: NameType,
    type: string,
    data: any,
    option?: {
        waitReadyTimeout?: number;
        timeout?: number;
    },
): Promise<{
    code: number;
    msg: string;
    data?: any;
}> => {
    option = Object.assign(
        {
            waitReadyTimeout: 10 * 1000,
            timeout: 60 * 1000,
        },
        option,
    );
    return new Promise((resolve, reject) => {
        const id = StrUtil.randomString(32);
        const timer = setTimeout(() => {
            ipcMain.removeListener(listenerKey, listener);
            resolve({ code: -1, msg: "timeout" });
        }, option.timeout);
        const listener = (_, result) => {
            clearTimeout(timer);
            resolve(result);
            return true;
        };
        const listenerKey = "event:callPage:" + id;
        ipcMain.once(listenerKey, listener);
        const payload = {
            type,
            data,
            option: {
                waitReadyTimeout: option.waitReadyTimeout,
            },
        };
        if (!send(name, "CALL_PAGE", payload, id)) {
            clearTimeout(timer);
            ipcMain.removeListener(listenerKey, listener);
            resolve({ code: -1, msg: "send failed" });
        }
    });
};

ipcMain.handle(
    "event:callPage",
    async (_, name: string, type: string, data: any, option?: {}) => {
        return callPage(name, type, data, option);
    },
);

let onChannelIsListen = false;
let channelOnCallback = {};

const sendChannel = (channel: string, data: any) => {
    send("main", "CHANNEL", { channel, data });
};

const onChannel = (channel: string, callback: (data: any) => void) => {
    if (!channelOnCallback[channel]) {
        channelOnCallback[channel] = [];
    }
    channelOnCallback[channel].push(callback);
    if (!onChannelIsListen) {
        onChannelIsListen = true;
        ipcMain.handle("event:channelSend", (event, channel_, data) => {
            if (channelOnCallback[channel_]) {
                channelOnCallback[channel_].forEach(
                    (callback: (data: any) => void) => {
                        callback(data);
                    },
                );
            }
        });
    }
};

const offChannel = (channel: string, callback: (data: any) => void) => {
    if (channelOnCallback[channel]) {
        channelOnCallback[channel] = channelOnCallback[channel].filter(
            (item: (data: any) => void) => {
                return item !== callback;
            },
        );
    }
    if (channelOnCallback[channel].length === 0) {
        delete channelOnCallback[channel];
    }
};

export default {
    init,
    send,
};

export const Events = {
    broadcast,
    send,
    sendRaw,
    sendChannel,
    callPage,
    onChannel,
    offChannel,
};


================================================
FILE: electron/mapi/event/render.ts
================================================
import { ipcRenderer } from "electron";

const init = () => {};

const send = (name: string, type: string, data: any = {}) => {
    return ipcRenderer.invoke("event:send", name, type, data).then();
};

const callPage = async (name: string, type: string, data: any, option: any) => {
    return ipcRenderer.invoke("event:callPage", name, type, data, option);
};

const channelSend = async (channel: string, data: any) => {
    return ipcRenderer.invoke("event:channelSend", channel, data);
};

export default {
    init,
    send,
    callPage,
    channelSend,
};


================================================
FILE: electron/mapi/file/index.ts
================================================
import fs, { createWriteStream } from "node:fs";
import path from "node:path";
import { Readable } from "node:stream";
import { ReadableStream } from "node:stream/web";
import { EncodeUtil, StrUtil, TimeUtil } from "../../lib/util";
import Apps from "../app";
import { ConfigIndex } from "../config";
import { AppEnv, waitAppEnvReady } from "../env";
import { Log } from "../log";
import electron from "electron";
import { finished } from "stream/promises";

const nodePath = path;

const toNodeReadableStream = (stream: any) => {
    if (stream instanceof ReadableStream) {
        // 已经是 Node.js 版本的 WHATWG ReadableStream
        return Readable.fromWeb(stream);
    }
    if (typeof stream.getReader === "function") {
        // 浏览器版本 → 包装成 Node.js 兼容的
        const nodeStream = new ReadableStream({
            async pull(controller) {
                const reader = stream.getReader();
                while (true) {
                    const { done, value } = await reader.read();
                    if (done) break;
                    controller.enqueue(value);
                }
                controller.close();
            },
        });
        return Readable.fromWeb(nodeStream);
    }
    throw new Error("Unsupported stream type");
};

const toWebReadableStream = (stream: any) => {
    const reader = stream[Symbol.asyncIterator]();
    return new window.ReadableStream({
        async pull(controller) {
            const { value, done } = await reader.next();
            if (done) {
                controller.close();
            } else {
                controller.enqueue(value);
            }
        },
    });
};

const root = () => {
    return AppEnv.dataRoot;
};

const absolutePath = (path: string) => {
    return `ABS://${path}`;
};

const fullPath = async (path: string) => {
    await waitAppEnvReady();
    if (path.startsWith("ABS://")) {
        return path.replace(/^ABS:\/\//, "");
    }
    return nodePath.join(root(), path);
};

const exists = async (
    path: string,
    option?: { isDataPath?: boolean },
): Promise<boolean> => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    return new Promise((resolve, reject) => {
        fs.stat(fp, (err, stat) => {
            if (err) {
                resolve(false);
            } else {
                resolve(true);
            }
        });
    });
};

const isDirectory = async (path: string, option?: { isDataPath?: boolean }) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!fs.existsSync(fp)) {
        return false;
    }
    return fs.statSync(fp).isDirectory();
};

const mkdir = async (path: string, option?: { isDataPath?: boolean }) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!fs.existsSync(fp)) {
        fs.mkdirSync(fp, { recursive: true });
    }
};

const list = async (path: string, option?: { isDataPath?: boolean }) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!fs.existsSync(fp)) {
        return [];
    }
    const files = fs.readdirSync(fp);
    return files.map((file) => {
        const stat = fs.statSync(nodePath.join(fp, file));
        let f = {
            name: file,
            pathname: nodePath.join(fp, file),
            isDirectory: stat.isDirectory(),
            size: stat.size,
            lastModified: stat.mtimeMs,
        };
        return f;
    });
};

const listAll = async (path: string, option?: { isDataPath?: boolean }) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!fs.existsSync(fp)) {
        return [];
    }
    const listDirectory = (path: string, basePath: string = "") => {
        let files = [];
        const list = fs.readdirSync(path);
        for (let file of list) {
            const stat = fs.statSync(nodePath.join(path, file));
            let fPath = nodePath.join(basePath, file);
            fPath = fPath.replace(/\\/g, "/");
            let f = {
                name: file,
                path: fPath,
                isDirectory: stat.isDirectory(),
                size: stat.size,
                lastModified: stat.mtimeMs,
            };
            if (f.isDirectory) {
                files = files.concat(
                    listDirectory(nodePath.join(path, file), f.path),
                );
                continue;
            }
            files.push(f);
        }
        return files;
    };
    return listDirectory(fp);
};

const write = async (
    path: string,
    data: any,
    option?: { isDataPath?: boolean },
) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    const fullPathDir = nodePath.dirname(fp);
    if (!fs.existsSync(fullPathDir)) {
        fs.mkdirSync(fullPathDir, { recursive: true });
    }
    if (typeof data === "string") {
        data = {
            content: data,
        };
    }
    const f = fs.openSync(fp, "w");
    fs.writeSync(f, data.content);
    fs.closeSync(f);
};

const writeStream = async (
    path: string,
    data: any,
    option?: { isDataPath?: boolean },
) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    const fullPathDir = nodePath.dirname(fp);
    if (!fs.existsSync(fullPathDir)) {
        fs.mkdirSync(fullPathDir, { recursive: true });
    }
    if (electron.ipcRenderer) {
        data = toNodeReadableStream(data);
    }
    const fileStream = createWriteStream(fp);
    data.pipe(fileStream);
    await finished(fileStream);
};

const writeBuffer = async (
    path: string,
    data: any,
    option?: { isDataPath?: boolean },
) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    const fullPathDir = nodePath.dirname(fp);
    if (!fs.existsSync(fullPathDir)) {
        fs.mkdirSync(fullPathDir, { recursive: true });
    }
    const f = fs.openSync(fp, "w");
    fs.writeSync(f, data);
    fs.closeSync(f);
};

const read = async (
    path: string,
    option?: {
        isDataPath?: boolean;
        encoding?: string;
    },
) => {
    option = Object.assign(
        {
            isDataPath: false,
            encoding: "utf8",
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!fs.existsSync(fp)) {
        return null;
    }
    const f = fs.openSync(fp, "r");
    const content = fs.readFileSync(f, {
        encoding: option.encoding as BufferEncoding,
    });
    fs.closeSync(f);
    return content;
};

const readBuffer = async (
    path: string,
    option?: { isDataPath?: boolean },
): Promise<Buffer> => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!fs.existsSync(fp)) {
        return null;
    }
    return new Promise((resolve, reject) => {
        fs.readFile(fp, (err, data) => {
            if (err) {
                reject(err);
                return;
            }
            resolve(data);
        });
    });
};

const readStream = async (path: string, option?: { isDataPath?: boolean }) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!fs.existsSync(fp)) {
        throw `FileNotFound: ${fp}`;
    }
    const stream = fs.createReadStream(fp);
    if (electron.ipcRenderer) {
        return toWebReadableStream(stream);
    }
    return stream;
};

const readLine = async (
    path: string,
    callback: (line: string) => void,
    option?: {
        isDataPath?: boolean;
    },
) => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!fs.existsSync(fp)) {
        return;
    }
    return new Promise((resolve, reject) => {
        const f = fs.createReadStream(fp);
        let remaining = "";
        f.on("data", (chunk) => {
            remaining += chunk;
            let index = remaining.indexOf("\n");
            let last = 0;
            while (index > -1) {
                let line = remaining.substring(last, index);
                last = index + 1;
                callback(line);
                index = remaining.indexOf("\n", last);
            }
            remaining = remaining.substring(last);
        });
        f.on("end", () => {
            if (remaining.length > 0) {
                callback(remaining);
            }
            resolve(undefined);
        });
    });
};

const clean = async (paths: string[], option?: { isDataPath?: boolean }) => {
    if (!paths || !Array.isArray(paths) || paths.length === 0) {
        return;
    }
    for (const path of paths) {
        try {
            await deletes(path, option);
        } catch (e) {
            Log.error(`CleanError: ${path}`, e);
        }
    }
};

const deletes = async (
    path: string,
    option?: { isDataPath?: boolean },
): Promise<void> => {
    option = Object.assign(
        {
            isDataPath: false,
        },
        option,
    );
    let fp = path;
    if (option.isDataPath) {
        fp = await fullPath(path);
    }
    if (!(await exists(fp, { isDataPath: false }))) {
        return;
    }
    return new Promise((resolve, reject) => {
        fs.stat(fp, (err, stat) => {
            if (err) {
                reject(err);
                return;
            }
            if (stat.isDirectory()) {
                fs.rmdir(fp, { recursive: true }, (err) => {
                    if (err) {
                        reject(err);
                        return;
                    }
                    resolve(undefined);
                });
            } else {
                fs.unlink(fp, (err) => {
                    if (err) {
                        reject(err);
                        return;
                    }
                    resolve(undefined);
                });
            }
        });
    });
};
const rename = async (
    pathOld: string,
    pathNew: string,
    option?: {
        isDataPath?: boolean;
        overwrite?: boolean;
    },
) => {
    option = Object.assign(
        {
            isDataPath: false,
            overwrite: false,
        },
        option,
    );
    let fullPathOld = pathOld;
    let fullPathNew = pathNew;
    if (option.isDataPath) {
        fullPathOld = await fullPath(pathOld);
        fullPathNew = await fullPath(pathNew);
    }
    if (!fs.existsSync(fullPathOld)) {
        throw `Rename.FileNotFound - ${fullPathOld}`;
    }
    if (fs.existsSync(fullPathNew)) {
        if (!option.overwrite) {
            throw new Error(`FileAlreadyExists:${fullPathNew}`);
        }
        fs.unlinkSync(fullPathNew);
    }
    const dir = nodePath.dirname(fullPathNew);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
    let success = false;
    try {
        fs.renameSync(fullPathOld, fullPathNew);
        success = true;
    } catch (e) {}
    if (!success) {
        // cross-device link not permitted, rename
        fs.copyFileSync(fullPathOld, fullPathNew);
        fs.unlinkSync(fullPathOld);
    }
};

const copy = async (
    pathOld: string,
    pathNew: string,
    option?: {
        isDataPath?: boolean;
        overwrite?: boolean;
    },
) => {
    option = Object.assign(
        {
            isDataPath: false,
            overwrite: false,
        },
        option,
    );
    let fullPathOld = pathOld;
    let fullPathNew = pathNew;
    if (option.isDataPath) {
        fullPathOld = await fullPath(pathOld);
        fullPathNew = await fullPath(pathNew);
    }
    if (!fs.existsSync(fullPathOld)) {
        throw `Copy.FileNotFound - ${fullPathOld}`;
    }
    if (fs.existsSync(fullPathNew)) {
        if (option.overwrite) {
            await deletes(fullPathNew, { isDataPath: false });
        } else {
            throw `Copy.FileAlreadyExists - ${fullPathNew}`;
        }
    }
    // console.log('copy', fullPathOld, fullPathNew)
    const dir = nodePath.dirname(fullPathNew);
    if (!fs.existsSync(dir)) {
        fs.mkdirSync(dir, { recursive: true });
    }
    fs.copyFileSync(fullPathOld, fullPathNew);
};

const hubRootDefault = async () => {
    await waitAppEnvReady();
    return path.join(root(), "hub");
};

const hubRoot = async (): Promise<string> => {
    const hubDirDefault = await hubRootDefault();
    let hubDir = await ConfigIndex.get("hubRoot", "");
    if (!hubDir) {
        hubDir = hubDirDefault;
    }
    if (!fs.existsSync(hubDir)) {
        fs.mkdirSync(hubDir, { recursive: true });
    }
    return hubDir;
};

const _getHubSavePath = async (
    hubRoot: string,
    saveGroup: string,
    savePath: string,
    saveParam: {
        [key: string]: any;
    },
    ext: string,
    autoCreateDir: boolean = false,
) => {
    if (!saveGroup) {
        saveGroup = "file";
    }
    if (!savePath) {
        savePath = path.join(
            saveGroup,
            "{year}{month}{day}",
            "{hour}{minute}_{second}_{random}",
        );
    }
    savePath = savePath.replace(/\\/g, "/");
    if (savePath.endsWith(`.${ext}`)) {
        savePath = savePath.substring(0, savePath.length - ext.length - 1);
    }
    for (const key in saveParam) {
        // only allow alphanumeric, Chinese characters, and hyphens
        saveParam[key] = saveParam[key]
            .toString()
            .replace(/[^\w\u4e00-\u9fa5\-]/g, "");
        // length limit
        if (saveParam[key].length > 100) {
            saveParam[key] = saveParam[key].substring(0, 100);
        }
    }
    const param = {
        year: TimeUtil.replacePattern("{year}"),
        month: TimeUtil.replacePattern("{month}"),
        day: TimeUtil.replacePattern("{day}"),
        hour: TimeUtil.replacePattern("{hour}"),
        minute: TimeUtil.replacePattern("{minute}"),
        second: TimeUtil.replacePattern("{second}"),
        random: StrUtil.randomString(32),
        ...saveParam,
    };
    savePath = savePath.replace(/\{(\w+)\}/g, (match, key) => {
        return param[key] || key;
    });
    while (
        await exists(path.join(hubRoot, savePath + `.${ext}`), {
            isDataPath: false,
        })
    ) {
        savePath = savePath + `-${StrUtil.randomString(3)}`;
    }
    if (autoCreateDir) {
        const savePathFull = path.join(hubRoot, savePath);
        const dir = nodePath.dirname(savePathFull);
        if (!(await exists(dir, { isDataPath: false }))) {
            fs.mkdirSync(dir, { recursive: true });
        }
    }
    return `${savePath}.${ext}`;
};

const hubDelete = async (
    file: string,
    option?: {
        isDataPath?: boolean;
        ignoreWhenNotInHub?: boolean;
        tryLaterWhenFailed?: boolean;
    },
) => {
    option = Object.assign(
        {
            isDataPath: false,
            ignoreWhenNotInHub: true,
            tryLaterWhenFailed: true,
        },
        option,
    );
    let fp = file;
    const hubRoot_ = await h
Download .txt
gitextract_d_g1kj1z/

├── .editorconfig
├── .github/
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug_report.md
│   │   └── help_wanted.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   └── workflows/
│       ├── build.yml
│       ├── main-build.yml
│       └── tag-release.yml
├── .gitignore
├── .npmrc
├── .nvmrc
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── cli/
│   ├── cmd/
│   │   ├── plugin.go
│   │   ├── root.go
│   │   └── version.go
│   ├── go.mod
│   ├── go.sum
│   ├── internal/
│   │   ├── client.go
│   │   └── config.go
│   └── main.go
├── electron/
│   ├── config/
│   │   ├── common.ts
│   │   ├── contextMenu.ts
│   │   ├── icon.ts
│   │   ├── lang.ts
│   │   ├── menu.ts
│   │   ├── tray.ts
│   │   └── window.ts
│   ├── declarations/
│   │   ├── electron.d.ts
│   │   └── svg.d.ts
│   ├── electron-env.d.ts
│   ├── lib/
│   │   ├── api.ts
│   │   ├── devtools.ts
│   │   ├── env-main.ts
│   │   ├── env.ts
│   │   ├── hooks.ts
│   │   ├── permission.ts
│   │   ├── pinyin-util.ts
│   │   ├── process.ts
│   │   └── util.ts
│   ├── main/
│   │   ├── fastPanel.ts
│   │   └── index.ts
│   ├── mapi/
│   │   ├── app/
│   │   │   ├── icons.ts
│   │   │   ├── index.ts
│   │   │   ├── lib/
│   │   │   │   └── position.ts
│   │   │   ├── loading.ts
│   │   │   ├── main.ts
│   │   │   ├── render.ts
│   │   │   ├── setup.ts
│   │   │   └── toast.ts
│   │   ├── config/
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── db/
│   │   │   ├── db.ts
│   │   │   ├── main.ts
│   │   │   ├── migration.ts
│   │   │   ├── render.ts
│   │   │   └── type.d.ts
│   │   ├── env.ts
│   │   ├── event/
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── file/
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── httpserver/
│   │   │   └── main.ts
│   │   ├── keys/
│   │   │   ├── main.ts
│   │   │   └── type.ts
│   │   ├── kvdb/
│   │   │   ├── kvdb.ts
│   │   │   ├── main.ts
│   │   │   ├── render.ts
│   │   │   ├── types.ts
│   │   │   ├── version.ts
│   │   │   └── webdav.ts
│   │   ├── log/
│   │   │   ├── beacon-render.ts
│   │   │   ├── beacon.ts
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── main.ts
│   │   ├── manager/
│   │   │   ├── automation/
│   │   │   │   └── index.ts
│   │   │   ├── backend/
│   │   │   │   └── index.ts
│   │   │   ├── clipboard/
│   │   │   │   ├── clipboardFiles.ts
│   │   │   │   └── index.ts
│   │   │   ├── code/
│   │   │   │   └── index.ts
│   │   │   ├── config/
│   │   │   │   └── config.ts
│   │   │   ├── editor/
│   │   │   │   └── index.ts
│   │   │   ├── hotkey/
│   │   │   │   ├── handle.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── simulate.ts
│   │   │   ├── lib/
│   │   │   │   ├── cache.ts
│   │   │   │   └── hooks.ts
│   │   │   ├── main.ts
│   │   │   ├── manager.ts
│   │   │   ├── plugin/
│   │   │   │   ├── colorPicker.ts
│   │   │   │   ├── event.ts
│   │   │   │   ├── http.ts
│   │   │   │   ├── httpMCP.ts
│   │   │   │   ├── index.ts
│   │   │   │   ├── llm.ts
│   │   │   │   ├── log.ts
│   │   │   │   ├── permission.ts
│   │   │   │   ├── screenCapture.ts
│   │   │   │   ├── screenRecord.ts
│   │   │   │   └── sdk.ts
│   │   │   ├── render.ts
│   │   │   ├── storage/
│   │   │   │   └── index.ts
│   │   │   ├── system/
│   │   │   │   ├── asset/
│   │   │   │   │   └── icon.ts
│   │   │   │   ├── index.ts
│   │   │   │   └── plugin/
│   │   │   │       ├── app/
│   │   │   │       │   ├── linux/
│   │   │   │       │   │   ├── icon.ts
│   │   │   │       │   │   ├── index.ts
│   │   │   │       │   │   └── title.ts
│   │   │   │       │   ├── mac/
│   │   │   │       │   │   ├── icon.ts
│   │   │   │       │   │   ├── index.ts
│   │   │   │       │   │   └── title.ts
│   │   │   │       │   ├── type.ts
│   │   │   │       │   ├── util/
│   │   │   │       │   │   └── index.ts
│   │   │   │       │   └── win/
│   │   │   │       │       ├── icon.ts
│   │   │   │       │       ├── index.ts
│   │   │   │       │       └── title.ts
│   │   │   │       ├── app.ts
│   │   │   │       ├── file.ts
│   │   │   │       ├── store/
│   │   │   │       │   ├── action.ts
│   │   │   │       │   └── index.ts
│   │   │   │       ├── store.ts
│   │   │   │       ├── system/
│   │   │   │       │   └── action.ts
│   │   │   │       └── system.ts
│   │   │   ├── type.ts
│   │   │   └── window/
│   │   │       ├── index.ts
│   │   │       └── remoteWeb.ts
│   │   ├── misc/
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── protocol/
│   │   │   └── main.ts
│   │   ├── render.ts
│   │   ├── statistics/
│   │   │   └── render.ts
│   │   ├── storage/
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── ui/
│   │   │   ├── index.ts
│   │   │   └── render.ts
│   │   ├── updater/
│   │   │   ├── index.ts
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   ├── user/
│   │   │   ├── main.ts
│   │   │   └── render.ts
│   │   └── util.ts
│   ├── page/
│   │   ├── about.ts
│   │   ├── feedback.ts
│   │   ├── guide.ts
│   │   ├── index.ts
│   │   ├── log.ts
│   │   ├── monitor.ts
│   │   ├── payment.ts
│   │   ├── setup.ts
│   │   └── user.ts
│   ├── preload/
│   │   ├── focusany.ts
│   │   ├── index.ts
│   │   └── plugin.ts
│   └── resources/
│       └── build/
│           ├── entitlements.mac.plist
│           ├── logo.icns
│           └── logo_1024x1024.psd
├── electron-builder.json5
├── entitlements.mac.plist
├── index.html
├── package.json
├── page/
│   ├── about.html
│   ├── detachWindow.html
│   ├── fastPanel.html
│   ├── feedback.html
│   ├── guide.html
│   ├── log.html
│   ├── monitor.html
│   ├── payment.html
│   ├── setup.html
│   ├── store.html
│   ├── system.html
│   ├── user.html
│   └── workflow.html
├── postcss.config.js
├── public/
│   ├── iconfont/
│   │   ├── iconfont.css
│   │   ├── iconfont.js
│   │   └── iconfont.json
│   └── static/
│       └── pluginEmpty.html
├── scripts/
│   ├── build_optimize.cjs
│   ├── common.cjs
│   ├── icon_convert.sh
│   ├── init.sh
│   └── notarize.cjs
├── sdk/
│   ├── .babelrc
│   ├── .github/
│   │   └── workflows/
│   │       └── tag-release.yml
│   ├── .gitignore
│   ├── .npmignore
│   ├── .nvmrc
│   ├── README.md
│   ├── bin/
│   │   └── command.ts
│   ├── config.schema.json
│   ├── electron-browser-window.d.ts
│   ├── electron.d.ts
│   ├── focusany-shim.d.ts
│   ├── focusany-shim.ts
│   ├── focusany.d.ts
│   ├── index.d.ts
│   ├── index.ts
│   ├── package.json
│   ├── shim.html
│   ├── tests/
│   │   └── config.json
│   └── tsconfig.json
├── src/
│   ├── App.vue
│   ├── api/
│   │   ├── types/
│   │   │   └── base.ts
│   │   └── user.ts
│   ├── app/
│   │   ├── dragWindow.ts
│   │   └── locale.ts
│   ├── components/
│   │   ├── AppQuitConfirm.vue
│   │   ├── PageNav.vue
│   │   ├── Setting/
│   │   │   ├── SettingAbout.vue
│   │   │   ├── SettingBasic.vue
│   │   │   ├── SettingEnv.vue
│   │   │   └── components/
│   │   │       └── SettingEnvHubRoot.vue
│   │   ├── TextTruncateView.vue
│   │   └── common/
│   │       ├── AudioPlayer.vue
│   │       ├── CodeViewer.vue
│   │       ├── CodeViewerDialog.vue
│   │       ├── DataConfigDialogButton.vue
│   │       ├── DragPasteContainer.vue
│   │       ├── FeedbackTicketButton.vue
│   │       ├── FileExt.vue
│   │       ├── FileLogViewer.vue
│   │       ├── FilesSelector.vue
│   │       ├── HtmlViewer.vue
│   │       ├── InputInlineEditor.vue
│   │       ├── LogViewer.vue
│   │       ├── LogViewerDialog.vue
│   │       ├── MEmpty.vue
│   │       ├── MLoading.vue
│   │       ├── PageWebviewStatus.vue
│   │       ├── ProUpgrade.vue
│   │       ├── SettingItemYesNo.vue
│   │       ├── SettingItemYesNoDefault.vue
│   │       ├── TaskBizStatus.vue
│   │       ├── UpdaterButton.vue
│   │       ├── VideoPlayer.vue
│   │       ├── WebFileSelectButton.vue
│   │       ├── dataConfig.ts
│   │       ├── index.ts
│   │       └── util.ts
│   ├── config.ts
│   ├── declarations/
│   │   ├── svg.d.ts
│   │   └── type.d.ts
│   ├── entry/
│   │   ├── Page.vue
│   │   ├── about.ts
│   │   ├── detachWindow.ts
│   │   ├── fastPanel.ts
│   │   ├── feedback.ts
│   │   ├── guide.ts
│   │   ├── log.ts
│   │   ├── monitor.ts
│   │   ├── payment.ts
│   │   ├── setup.ts
│   │   ├── store.ts
│   │   ├── system.ts
│   │   ├── user.ts
│   │   └── workflow.ts
│   ├── hooks/
│   │   └── user.ts
│   ├── lang/
│   │   ├── en-US.json
│   │   ├── index.ts
│   │   └── zh-CN.json
│   ├── layouts/
│   │   ├── Main.vue
│   │   └── Raw.vue
│   ├── lib/
│   │   ├── api.ts
│   │   ├── audio.ts
│   │   ├── components/
│   │   │   └── Prompt.vue
│   │   ├── dialog.ts
│   │   ├── env.ts
│   │   ├── error.ts
│   │   ├── event.ts
│   │   ├── file.ts
│   │   ├── markdown.ts
│   │   ├── storage.ts
│   │   ├── toggle.ts
│   │   ├── ui.ts
│   │   └── util.ts
│   ├── main.ts
│   ├── module/
│   │   └── Model/
│   │       ├── ModelGenerateButton.vue
│   │       ├── ModelGenerator.vue
│   │       ├── ModelPromptDataConfigButton.vue
│   │       ├── ModelSelector.vue
│   │       ├── ModelSetting.vue
│   │       ├── ModelSettingDialog.vue
│   │       ├── components/
│   │       │   ├── ModelAddDialog.vue
│   │       │   ├── ModelEditDialog.vue
│   │       │   ├── ProviderAddDialog.vue
│   │       │   ├── ProviderEditDialog.vue
│   │       │   └── ProviderTestDialog.vue
│   │       ├── models.ts
│   │       ├── provider/
│   │       │   ├── driver/
│   │       │   │   ├── base.ts
│   │       │   │   └── openai.ts
│   │       │   └── provider.ts
│   │       ├── providers.ts
│   │       ├── store/
│   │       │   └── model.ts
│   │       └── types.ts
│   ├── pages/
│   │   ├── DetachWindow/
│   │   │   └── operate.ts
│   │   ├── FastPanel/
│   │   │   ├── FastPanelResult.vue
│   │   │   ├── FastPanelSearch.vue
│   │   │   └── Lib/
│   │   │       └── resultOperate.ts
│   │   ├── Home.vue
│   │   ├── Main/
│   │   │   ├── Components/
│   │   │   │   ├── ResultActionCodeError.vue
│   │   │   │   ├── ResultActionCodeItemList.vue
│   │   │   │   ├── ResultActionCodeLoading.vue
│   │   │   │   ├── ResultItem.vue
│   │   │   │   ├── ResultLoading.vue
│   │   │   │   └── ResultWindowItem.vue
│   │   │   ├── Lib/
│   │   │   │   ├── entryListener.ts
│   │   │   │   ├── mainOperate.ts
│   │   │   │   ├── resultOperate.ts
│   │   │   │   ├── resultResize.ts
│   │   │   │   ├── searchOperate.ts
│   │   │   │   └── viewOperate.ts
│   │   │   ├── MainResult.vue
│   │   │   └── MainSearch.vue
│   │   ├── PageAbout.vue
│   │   ├── PageDetachWindow.vue
│   │   ├── PageFastPanel.vue
│   │   ├── PageFeedback.vue
│   │   ├── PageGuide.vue
│   │   ├── PageLog.vue
│   │   ├── PageMonitor.vue
│   │   ├── PagePayment.vue
│   │   ├── PageSetup.vue
│   │   ├── PageStore.vue
│   │   ├── PageSystem.vue
│   │   ├── PageUser.vue
│   │   ├── PageWorkflow.vue
│   │   ├── Setting.vue
│   │   └── System/
│   │       ├── SystemAbout.vue
│   │       ├── SystemAction.vue
│   │       ├── SystemData.vue
│   │       ├── SystemFile.vue
│   │       ├── SystemLaunch.vue
│   │       ├── SystemMCP.vue
│   │       ├── SystemModel.vue
│   │       ├── SystemPlugin.vue
│   │       ├── SystemSetting.vue
│   │       ├── SystemUser.vue
│   │       └── components/
│   │           ├── ActionTypeIcon.vue
│   │           ├── HotkeyInput.vue
│   │           ├── SystemActionMatchDetailDialog.vue
│   │           ├── SystemDataBackup/
│   │           │   ├── WebDavManage.vue
│   │           │   └── WebDavManageSettingDialog.vue
│   │           ├── SystemDataBackupDialog.vue
│   │           ├── SystemDataViewDetailDialog.vue
│   │           ├── SystemDataViewDialog.vue
│   │           └── type.ts
│   ├── router.ts
│   ├── store/
│   │   ├── index.ts
│   │   └── modules/
│   │       ├── app.ts
│   │       ├── manager.ts
│   │       ├── setting.ts
│   │       ├── task.ts
│   │       └── user.ts
│   ├── style.less
│   ├── task/
│   │   └── index.ts
│   ├── types/
│   │   └── Manager.ts
│   └── vite-env.d.ts
├── tailwind.config.js
├── tsconfig.json
├── tsconfig.node.json
├── vite.config.flat.txt
└── vite.config.ts
Download .txt
SYMBOL INDEX (843 symbols across 104 files)

FILE: cli/cmd/plugin.go
  function init (line 30) | func init() {

FILE: cli/cmd/root.go
  function Execute (line 12) | func Execute(version string) {
  function init (line 25) | func init() {

FILE: cli/internal/client.go
  function DoRequest (line 12) | func DoRequest(cfg *AuthConfig, method string, urlPath string, body any)...
  function PrintJSON (line 55) | func PrintJSON(v any) error {

FILE: cli/internal/config.go
  type AuthConfig (line 12) | type AuthConfig struct
  function userDataDir (line 19) | func userDataDir() (string, error) {
  function LoadAuthConfig (line 48) | func LoadAuthConfig() (*AuthConfig, error) {

FILE: cli/main.go
  function main (line 8) | func main() {

FILE: electron/declarations/electron.d.ts
  type BrowserView (line 2) | interface BrowserView {
  type BrowserWindow (line 7) | interface BrowserWindow {

FILE: electron/electron-env.d.ts
  type ProcessEnv (line 5) | interface ProcessEnv {

FILE: electron/lib/api.ts
  type ResultType (line 3) | type ResultType<T> = {

FILE: electron/lib/devtools.ts
  method setEnable (line 10) | setEnable(enable: boolean) {
  method getWindow (line 13) | getWindow(win: BrowserWindow | BrowserView) {
  method getOrCreateWindow (line 16) | getOrCreateWindow(name: string, win: BrowserWindow | BrowserView) {
  method getLargestDisplay (line 50) | getLargestDisplay(): Electron.Display {
  method getDisplayPosition (line 59) | getDisplayPosition(): {
  method register (line 84) | register(name: string, win: BrowserWindow | BrowserView) {
  method autoShow (line 90) | autoShow(win: BrowserWindow | BrowserView) {

FILE: electron/lib/env-main.ts
  constant MAIN_DIST (line 11) | const MAIN_DIST = path.join(process.env.APP_ROOT, "dist-electron");
  constant RENDERER_DIST (line 12) | const RENDERER_DIST = path.join(process.env.APP_ROOT, "dist");
  constant VITE_DEV_SERVER_URL (line 13) | const VITE_DEV_SERVER_URL = process.env.VITE_DEV_SERVER_URL;

FILE: electron/lib/hooks.ts
  type HookType (line 3) | type HookType = never | "Show" | "Hide";

FILE: electron/lib/permission.ts
  method checkAccessibilityAccess (line 15) | async checkAccessibilityAccess(): Promise<boolean> {
  method askAccessibilityAccess (line 26) | async askAccessibilityAccess() {
  method checkScreenCaptureAccess (line 29) | async checkScreenCaptureAccess(): Promise<boolean> {
  method askScreenCaptureAccess (line 39) | async askScreenCaptureAccess() {

FILE: electron/lib/pinyin-util.ts
  method match (line 4) | match(input, keywords) {
  method mark (line 25) | mark(text) {

FILE: electron/lib/util.ts
  method base32Encode (line 21) | base32Encode(str: string) {
  method base32Decode (line 39) | base32Decode(str: string) {
  method base64Encode (line 59) | base64Encode(str: string) {
  method base64Decode (line 62) | base64Decode(str: string) {
  method md5 (line 65) | md5(str: string) {
  method aesEncode (line 68) | aesEncode(str: string, key: string) {
  method aesDecode (line 74) | aesDecode(str: string, key: string) {
  method fileXzipEncode (line 80) | async fileXzipEncode(pathname: string): Promise<string> {
  method fileXzipDecode (line 158) | async fileXzipDecode(pathname: string): Promise<string> {
  method convert (line 291) | convert(str: string, to?: string, from?: string) {
  method bufferToUtf8 (line 299) | bufferToUtf8(buffer: Buffer) {
  method detect (line 311) | detect(buffer: Uint8Array) {
  method randomString (line 318) | randomString(len: number = 32) {
  method uuid (line 327) | uuid() {
  method hashCode (line 337) | hashCode(str: string, length: number = 8) {
  method hashCodeWithDuplicateCheck (line 351) | hashCodeWithDuplicateCheck(
  method bigIntegerId (line 362) | bigIntegerId() {
  method ucFirst (line 368) | ucFirst(str: string) {
  method timestampInMs (line 375) | timestampInMs() {
  method timestamp (line 378) | timestamp() {
  method format (line 381) | format(time: number, format: string = "YYYY-MM-DD HH:mm:ss") {
  method formatDate (line 384) | formatDate(time: number) {
  method dateString (line 387) | dateString() {
  method datetimeString (line 390) | datetimeString() {
  method timestampDayStart (line 393) | timestampDayStart(msTimestamp?: number) {
  method replacePattern (line 398) | replacePattern(text: string) {
  method getMimeByExt (line 433) | getMimeByExt(ext: string, defaultMime: string = ""): string {
  method getMimeByPath (line 440) | getMimeByPath(p: string, defaultMime: string = ""): string {
  method streamToBase64 (line 444) | streamToBase64(stream: NodeJS.ReadableStream): Promise<string> {
  method bufferToBase64 (line 459) | bufferToBase64(buffer: Buffer) {
  method base64ToBuffer (line 468) | base64ToBuffer(base64: string): Buffer {
  method formatSize (line 474) | formatSize(size: number) {
  method md5 (line 485) | async md5(filePath: string) {
  method stringifyOrdered (line 503) | stringifyOrdered(obj: any) {
  method stringifyValueOrdered (line 506) | stringifyValueOrdered(obj: any) {
  method loadCommonJs (line 518) | async loadCommonJs(cjsPath: string, forceReload: boolean = true) {
  method _gc (line 543) | _gc() {
  method remember (line 551) | async remember<T extends any>(
  method get (line 567) | get(key: string) {
  method set (line 574) | set(key: string, value: any, ttl: number = 86400) {
  method forget (line 581) | forget(key: string) {
  method _gc (line 595) | _gc() {
  method get (line 605) | get(group: string, key: string) {
  method set (line 616) | set(group: string, key: string, value: any, ttl: number = 86400) {
  method forget (line 626) | forget(group: string, key: string) {
  method quotaPath (line 634) | quotaPath(p: string) {
  method parseCommandArgs (line 637) | parseCommandArgs(command: string) {
  method match (line 691) | match(v: string, match: string) {
  method compare (line 716) | compare(v1: string, v2: string) {
  method gt (line 730) | gt(v1: string, v2: string) {
  method ge (line 733) | ge(v1: string, v2: string) {
  method lt (line 736) | lt(v1: string, v2: string) {
  method sizeToPx (line 748) | sizeToPx(size: string, sizeFull: number) {
  method match (line 764) | match(regex: string, text: string) {
  method toHtml (line 782) | toHtml(markdown: string): string {
  type HotkeyModifierType (line 787) | type HotkeyModifierType =
  type HotkeyType (line 796) | type HotkeyType = { key: string; modifiers: HotkeyModifierType[] };
  method orderModifiers (line 799) | orderModifiers(modifiers: HotkeyModifierType[]) {
  method unifyObject (line 814) | unifyObject(hotkey: HotkeyType) {
  method unifyString (line 822) | unifyString(hotkey: string): HotkeyType {
  method unify (line 831) | unify(
  method getFromEvent (line 848) | getFromEvent(event: any): HotkeyType | null {
  method match (line 929) | match(hotkeysForMatch: HotkeyType[], hotkey: HotkeyType): boolean {

FILE: electron/main/fastPanel.ts
  method init (line 21) | init() {

FILE: electron/main/index.ts
  function createWindow (line 110) | async function createWindow() {

FILE: electron/mapi/app/lib/position.ts
  type PositionCache (line 3) | type PositionCache = {
  method getCache (line 13) | getCache(name: string): PositionCache {
  method get (line 25) | get(
  method set (line 75) | set(name: string, x: number, y: number): void {
  method getContextMenuPosition (line 80) | getContextMenuPosition(

FILE: electron/mapi/app/setup.ts
  method isOk (line 5) | async isOk() {
  method list (line 14) | async list() {
  method open (line 46) | async open(name: string) {

FILE: electron/mapi/db/main.ts
  method _check (line 19) | _check() {
  method execute (line 30) | async execute(sql: string, params: any = []): Promise<void> {
  method insert (line 44) | async insert(sql: string, params: any = []): Promise<string | number> {
  method first (line 59) | async first(sql: string, params: any = []): Promise<any> {
  method select (line 73) | async select(sql: string, params: any = []): Promise<any[]> {
  method update (line 87) | async update(sql: string, params: any = []): Promise<number> {
  method delete (line 102) | async delete(sql: string, params: any = []): Promise<number> {

FILE: electron/mapi/db/type.d.ts
  type DB (line 1) | type DB = {

FILE: electron/mapi/event/main.ts
  type NameType (line 8) | type NameType = "main" | "fastPanel" | string | WebContents;
  type EventType (line 9) | type EventType = "APP_READY" | "CALL_PAGE" | "CHANNEL" | "BROADCAST";
  type BroadcastType (line 10) | type BroadcastType =

FILE: electron/mapi/file/index.ts
  method pull (line 23) | async pull(controller) {
  method pull (line 41) | async pull(controller) {

FILE: electron/mapi/httpserver/main.ts
  method start (line 95) | async start() {
  method stop (line 118) | stop() {
  method getPort (line 128) | getPort() {
  method getToken (line 132) | getToken() {

FILE: electron/mapi/keys/type.ts
  type HotkeyMouseButtonEnum (line 1) | enum HotkeyMouseButtonEnum {
  type HotkeyKeyItem (line 6) | type HotkeyKeyItem = {
  type HotkeyKeySimpleItem (line 19) | type HotkeyKeySimpleItem = {
  type HotkeyMouseItem (line 24) | type HotkeyMouseItem = {

FILE: electron/mapi/kvdb/kvdb.ts
  class KVDB (line 21) | class KVDB {
    method constructor (line 29) | constructor() {
    method init (line 43) | init(): void {
    method getDocId (line 54) | getDocId(name: string, id: string): string {
    method replaceDocId (line 58) | replaceDocId(name: string, id: string): string {
    method errorInfo (line 62) | errorInfo(name: string, message: string): DBError {
    method checkDocSize (line 66) | private checkDocSize(doc: Doc) {
    method put (line 76) | async put(
    method putRaw (line 103) | async putRaw(doc: Doc): Promise<DBError | DocRes> {
    method get (line 118) | async get(name: string, id: string): Promise<Doc | null> {
    method getRaw (line 128) | async getRaw(id: string) {
    method remove (line 136) | async remove(name: string, doc: Doc | string) {
    method removeRaw (line 165) | async removeRaw(doc: Doc) {
    method bulkPut (line 173) | async bulkPut(
    method all (line 218) | async all(
    method allKeys (line 253) | async allKeys(
    method count (line 288) | async count(
    method postAttachment (line 317) | public async postAttachment(
    method getAttachment (line 346) | async getAttachment(name: string, docId: string, len = "0") {
    method getAttachmentRaw (line 357) | async getAttachmentRaw(docId: string, len = "0") {
    method dumpToFile (line 365) | public async dumpToFile(file: string, option?: {}): Promise<void> {
    method importFromFile (line 377) | public async importFromFile(file: string, option?: {}): Promise<void> {
    method dumpToWavDav (line 391) | public async dumpToWavDav(
    method importFromWebDav (line 408) | public async importFromWebDav(
    method load (line 429) | public async load(readableStream: any) {

FILE: electron/mapi/kvdb/types.ts
  type RevisionId (line 1) | type RevisionId = string;
  type Doc (line 3) | type Doc<T extends {} = Record<string, any>> = {
  type DocRes (line 9) | interface DocRes {
  type DBError (line 17) | interface DBError {
  type AllDocsOptions (line 27) | interface AllDocsOptions {

FILE: electron/mapi/kvdb/version.ts
  method _getExist (line 5) | async _getExist(name: string) {
  method update (line 17) | async update(name: string) {
  method insert (line 35) | async insert(name: string) {
  method remove (line 38) | async remove(name: string) {
  method shouldIgnore (line 56) | shouldIgnore(name: string) {

FILE: electron/mapi/kvdb/webdav.ts
  type WebDavOptions (line 7) | type WebDavOptions = {
  class WebDav (line 13) | class WebDav {
    method constructor (line 16) | constructor({ username, password, url }: WebDavOptions) {
    method checkConnection (line 25) | async checkConnection(): Promise<void> {
    method listDir (line 29) | async listDir(dir: string): Promise<string[]> {
    method dump (line 35) | async dump(kvdb: KVDB, file: string): Promise<void> {
    method import (line 56) | async import(kvdb: KVDB, file: string): Promise<void> {

FILE: electron/mapi/log/beacon-render.ts
  constant BEACON_URL (line 9) | const BEACON_URL = "https://g.tecmz.com/grow/load.gif";
  constant BEACON_APP (line 10) | const BEACON_APP = "focusany";
  type BeaconEvent (line 15) | interface BeaconEvent {

FILE: electron/mapi/log/beacon.ts
  constant BEACON_URL (line 11) | const BEACON_URL = "https://g.tecmz.com/grow/load.gif";
  constant BEACON_APP (line 12) | const BEACON_APP = "focusany";
  type BeaconEvent (line 16) | interface BeaconEvent {

FILE: electron/mapi/main.ts
  constant MAPI (line 36) | const MAPI = {
  method init (line 37) | async init() {
  method ready (line 45) | ready() {
  method destroy (line 50) | destroy() {

FILE: electron/mapi/manager/automation/index.ts
  method init (line 10) | init() {
  method track (line 15) | track() {
  method trackShouldIgnore (line 31) | trackShouldIgnore(win: Window): boolean {
  method activateLatestWindow (line 48) | async activateLatestWindow(): Promise<void> {
  method getActiveWindow (line 53) | async getActiveWindow(): Promise<ActiveWindow> {
  method typeString (line 71) | async typeString(text: string): Promise<void> {
  method typeKey (line 78) | async typeKey(key: string): Promise<void> {
  method mouseToggle (line 148) | async mouseToggle(
  method moveMouse (line 166) | async moveMouse(x: number, y: number): Promise<void> {
  method mouseClick (line 169) | async mouseClick(

FILE: electron/mapi/manager/backend/index.ts
  method run (line 9) | async run(
  method runAction (line 61) | async runAction(plugin: PluginRecord, action: ActionRecord, option?: {}) {

FILE: electron/mapi/manager/clipboard/index.ts
  method init (line 33) | async init() {
  method waitClipboardFree (line 46) | async waitClipboardFree() {
  method backupClipboard (line 51) | async backupClipboard() {
  method restoreClipboard (line 57) | async restoreClipboard() {
  method getSelectedContent (line 65) | async getSelectedContent(): Promise<ClipboardDataType | null> {
  method _setClipboardContent (line 76) | async _setClipboardContent(data: ClipboardDataType): Promise<void> {
  method _getClipboardContent (line 89) | async _getClipboardContent(): Promise<ClipboardDataType | null> {
  method pasteClipboardContent (line 113) | async pasteClipboardContent(data: ClipboardDataType): Promise<void> {
  method getClipboardContent (line 125) | async getClipboardContent(): Promise<ClipboardDataType | null> {
  method _watch (line 142) | _watch() {
  method monitorStart (line 163) | monitorStart() {
  method monitorStop (line 167) | monitorStop() {
  method encrypt (line 170) | encrypt(data: ClipboardHistoryRecord) {
  method decrypt (line 174) | decrypt(data: string): ClipboardHistoryRecord {
  method onChange (line 182) | async onChange(data: ClipboardDataType) {
  method list (line 214) | async list(limit: number = -1): Promise<ClipboardHistoryRecord[]> {
  method clear (line 269) | async clear() {
  method delete (line 272) | async delete(timestamp: number) {

FILE: electron/mapi/manager/code/index.ts
  method execute (line 7) | async execute(plugin: PluginRecord, action: ActionRecord, option?: {}) {

FILE: electron/mapi/manager/config/config.ts
  method clearCache (line 47) | async clearCache() {
  method get (line 56) | async get(): Promise<ConfigRecord> {
  method save (line 94) | async save(config: ConfigRecord): Promise<void> {
  method listDisabledActionMatch (line 118) | async listDisabledActionMatch() {
  method toggleDisabledActionMatch (line 128) | async toggleDisabledActionMatch(
  method listPinAction (line 165) | async listPinAction(): Promise<PluginActionRecord[]> {
  method getPinedActionSet (line 177) | async getPinedActionSet(): Promise<Set<string>> {
  method togglePinAction (line 185) | async togglePinAction(pluginName: string, actionName: string) {
  method listLaunch (line 211) | async listLaunch(): Promise<LaunchRecord[]> {
  method updateLaunch (line 223) | async updateLaunch(records: LaunchRecord[]) {
  method getCustomAction (line 249) | async getCustomAction(): Promise<Record<string, ActionRecord[]>> {
  method addCustomAction (line 259) | async addCustomAction(
  method updateCustomAction (line 286) | async updateCustomAction(customAction: Record<string, ActionRecord[]>) {
  method removeCustomAction (line 293) | async removeCustomAction(plugin: PluginRecord, name: string) {
  method clearCustomAction (line 303) | async clearCustomAction(pluginName: string) {
  method getHistoryAction (line 311) | async getHistoryAction(): Promise<PluginActionRecord[]> {
  method clearHistoryAction (line 323) | async clearHistoryAction() {
  method deleteHistoryAction (line 330) | async deleteHistoryAction(pluginName: string, actionName: string) {
  method addHistoryAction (line 342) | async addHistoryAction(plugin: PluginRecord, action: ActionRecord) {
  method getPluginConfigAll (line 364) | async getPluginConfigAll(): Promise<Record<string, PluginConfig>> {
  method getPluginConfig (line 376) | async getPluginConfig(pluginName: string): Promise<PluginConfig> {
  method setPluginConfigItem (line 380) | async setPluginConfigItem(pluginName: string, key: string, value: any) {
  method setPluginConfig (line 390) | async setPluginConfig(pluginName: string, config: PluginConfig) {

FILE: electron/mapi/manager/editor/index.ts
  method init (line 12) | async init() {}
  method ready (line 13) | async ready() {
  method getFaDataTypeCached (line 17) | async getFaDataTypeCached(file: string) {
  method filterFadType (line 36) | async filterFadType(files: FileItem[], types: string[]) {
  method openQueue (line 53) | async openQueue(filePath: string) {
  method openFileEditor (line 57) | async openFileEditor() {

FILE: electron/mapi/manager/hotkey/handle.ts
  method mainTrigger (line 6) | async mainTrigger() {
  method fastPanelTrigger (line 17) | async fastPanelTrigger() {
  method launch (line 27) | async launch(index: string) {

FILE: electron/mapi/manager/hotkey/index.ts
  type HotkeyKeyItem (line 9) | type HotkeyKeyItem = {
  type HotkeyKeySimpleItem (line 22) | type HotkeyKeySimpleItem = {
  type HotkeyMouseItem (line 29) | type HotkeyMouseItem = {
  method init (line 102) | init() {
  method destroy (line 264) | destroy() {
  method register (line 268) | async register() {
  method configInit (line 294) | async configInit() {
  method watch (line 348) | async watch() {
  method unwatch (line 351) | async unwatch() {
  method fire (line 356) | fire(eventName: string, ...args: any[]) {
  method on (line 374) | on(eventName: string, callback: Function) {
  method off (line 380) | off(eventName: string, callback: Function) {

FILE: electron/mapi/manager/hotkey/simulate.ts
  method toCode (line 8) | toCode(key: string) {
  method keyTap (line 11) | keyTap(key: number, modifiers?: number[]) {

FILE: electron/mapi/manager/lib/cache.ts
  method get (line 5) | async get(name: string, defaultValue: any = null) {
  method getIgnoreExpire (line 28) | async getIgnoreExpire(
  method set (line 66) | async set(name: string, value: any, expire: number = 0) {
  method forget (line 75) | async forget(name: string) {

FILE: electron/mapi/manager/lib/hooks.ts
  type PluginHookType (line 5) | type PluginHookType =
  type HookType (line 16) | type HookType =

FILE: electron/mapi/manager/manager.ts
  type SearchRequest (line 28) | type SearchRequest = {
  method createSearchRequest (line 39) | createSearchRequest(query: SearchQuery) {
  method getSearchRequestQuery (line 51) | getSearchRequestQuery(id: string) {
  method openPlugin (line 59) | async openPlugin(pluginName: string) {
  method openActionWindow (line 74) | async openActionWindow(type: "open" | "close", action: ActionRecord) {
  method openAction (line 77) | async openAction(action: ActionRecord) {
  method getPlugin (line 107) | async getPlugin(name: string) {
  method getPluginSync (line 115) | getPluginSync(name: string) {
  method listPlugin (line 123) | async listPlugin() {
  method listAction (line 137) | async listAction(request?: SearchRequest) {
  method searchOneAction (line 156) | async searchOneAction(
  method matchActionSimple (line 192) | async matchActionSimple(query: SearchQuery): Promise<ActionRecord[]> {
  method searchActions (line 208) | async searchActions(
  method matchActions (line 275) | async matchActions(
  method detachWindowActions (line 454) | async detachWindowActions(
  method historyActions (line 497) | async historyActions(
  method pinActions (line 516) | async pinActions(
  method sendBroadcast (line 535) | async sendBroadcast(pluginName: string, type: string, data: any) {
  method setNotice (line 545) | async setNotice(

FILE: electron/mapi/manager/plugin/event.ts
  method onResult (line 609) | onResult(result) {

FILE: electron/mapi/manager/plugin/http.ts
  function servePluginStatic (line 8) | async function servePluginStatic(req, res) {
  method init (line 45) | async init() {
  method getMcpServer (line 58) | async getMcpServer() {
  method url (line 64) | async url(pluginName: string, filePath: string): Promise<string> {

FILE: electron/mapi/manager/plugin/httpMCP.ts
  function serveMcpSSE (line 9) | async function serveMcpSSE(req, res) {
  function serveMcpRPC (line 23) | async function serveMcpRPC(req, res) {

FILE: electron/mapi/manager/plugin/index.ts
  type PluginInfo (line 39) | type PluginInfo = {
  method clearCache (line 54) | async clearCache() {
  method getInfo (line 58) | async getInfo(plugin: PluginRecord) {
  method normalAction (line 179) | normalAction(action: ActionRecord, plugin: PluginRecord) {
  method initIfNeed (line 231) | async initIfNeed(
  method configCheck (line 334) | async configCheck(config: any) {
  method parsePackage (line 364) | async parsePackage(file: string, option?: {}) {
  method installFromFileOrDir (line 396) | async installFromFileOrDir(fileOrPath: string, type?: PluginType) {
  method install (line 431) | async install(root: string, type: PluginType) {
  method refreshInstall (line 473) | async refreshInstall(name: string) {
  method uninstall (line 512) | async uninstall(name: string) {
  method getPluginInstalledVersion (line 541) | async getPluginInstalledVersion(name: string) {
  method isPluginInstalling (line 548) | async isPluginInstalling(name: string) {}
  method list (line 549) | async list(): Promise<PluginRecord[]> {
  method get (line 610) | async get(name: string) {
  method _readPluginInfo (line 618) | async _readPluginInfo(root: string) {
  method listAction (line 636) | async listAction() {
  method getViewSession (line 649) | async getViewSession(plugin: PluginRecord, name: string = null) {
  method clearViewSession (line 655) | async clearViewSession(plugin: PluginRecord) {
  method isDevelopmentCheck (line 661) | isDevelopmentCheck(

FILE: electron/mapi/manager/plugin/permission.ts
  method checkPermit (line 9) | checkPermit(
  method check (line 31) | check(

FILE: electron/mapi/manager/plugin/sdk.ts
  method isMacOs (line 17) | async isMacOs() {
  method isWindows (line 20) | async isWindows() {
  method isLinux (line 23) | async isLinux() {
  method getPlatformArch (line 26) | async getPlatformArch() {
  method isMainWindowShown (line 29) | async isMainWindowShown() {
  method hideMainWindow (line 32) | async hideMainWindow() {
  method showMainWindow (line 35) | async showMainWindow() {
  method isFastPanelWindowShown (line 38) | async isFastPanelWindowShown() {
  method showFastPanelWindow (line 41) | async showFastPanelWindow() {
  method hideFastPanelWindow (line 44) | async hideFastPanelWindow() {
  method showOpenDialog (line 47) | async showOpenDialog() {
  method showSaveDialog (line 50) | async showSaveDialog() {
  method getPluginRoot (line 53) | async getPluginRoot() {
  method getPluginConfig (line 56) | async getPluginConfig() {
  method getPluginInfo (line 59) | async getPluginInfo() {
  method getPluginEnv (line 62) | async getPluginEnv() {
  method getPath (line 65) | async getPath(
  method showToast (line 82) | async showToast(
  method showNotification (line 91) | async showNotification(body: string, clickActionName: string) {
  method showMessageBox (line 97) | async showMessageBox(
  method copyImage (line 110) | async copyImage(img: string) {
  method copyText (line 113) | async copyText(text: string) {
  method copyFile (line 116) | async copyFile(file: string) {
  method getClipboardText (line 119) | async getClipboardText() {
  method getClipboardImage (line 122) | async getClipboardImage() {
  method getClipboardFiles (line 125) | async getClipboardFiles(): Promise<
  method listClipboardItems (line 139) | async listClipboardItems(option?: { limit?: number }): Promise<
  method deleteClipboardItem (line 150) | async deleteClipboardItem(timestamp: number): Promise<void> {
  method clearClipboardItems (line 155) | async clearClipboardItems(): Promise<void> {
  method shellOpenExternal (line 158) | async shellOpenExternal(url: string) {
  method shellOpenPath (line 161) | async shellOpenPath(path: string) {
  method shellShowItemInFolder (line 164) | async shellShowItemInFolder(path: string) {
  method shellBeep (line 167) | async shellBeep() {
  method getFileIcon (line 170) | async getFileIcon(path: string) {
  method keyboardTap (line 174) | async keyboardTap(
  method typeString (line 183) | async typeString(text: string) {
  method mouseToggle (line 186) | async mouseToggle(
  method mouseMove (line 195) | async mouseMove(x: number, y: number) {
  method mouseClick (line 198) | async mouseClick(
  method getCursorScreenPoint (line 208) | async getCursorScreenPoint() {
  method getDisplayNearestPoint (line 211) | async getDisplayNearestPoint(point: { x: number; y: number }) {
  method createBrowserWindow (line 215) | async createBrowserWindow(url: string, options: any, callback: any) {
  method screenCapture (line 262) | async screenCapture(cb: Function) {
  method getNativeId (line 268) | getNativeId() {
  method getAppVersion (line 271) | getAppVersion() {
  method isDarkColors (line 274) | async isDarkColors() {
  method redirect (line 277) | async redirect(keywordsOrAction: string | string[], payload: any) {
  method getActions (line 283) | async getActions(names?: string[]) {
  method setAction (line 286) | async setAction(action: string) {
  method removeAction (line 289) | async removeAction(name: string) {
  method sendBackendEvent (line 292) | async sendBackendEvent(
  method registerCallPage (line 301) | registerCallPage(
  method callPage (line 314) | callPage(
  method setRemoteWebRuntime (line 321) | setRemoteWebRuntime(info: {
  method llmListModels (line 330) | async llmListModels(): Promise<
  method llmChat (line 342) | async llmChat(callInfo: {
  method logInfo (line 356) | logInfo(label: string, data?: any): void {
  method logError (line 360) | logError(label: string, data?: any): void {
  method logPath (line 364) | async logPath(): Promise<string> {
  method logShow (line 368) | logShow(): void {
  method addLaunch (line 372) | async addLaunch(
  method removeLaunch (line 384) | async removeLaunch(keyword: string): Promise<void> {
  method activateLatestWindow (line 388) | async activateLatestWindow(): Promise<void> {
  method getUser (line 392) | async getUser(): Promise<{
  method getUserAccessToken (line 400) | async getUserAccessToken(): Promise<{
  method exists (line 407) | async exists(path: string): Promise<boolean> {
  method read (line 410) | async read(path: string): Promise<string> {
  method write (line 413) | async write(path: string, data: string): Promise<void> {
  method remove (line 416) | async remove(path: string): Promise<void> {
  method ext (line 419) | async ext(path: string): Promise<string> {
  method writeTemp (line 422) | async writeTemp(
  method put (line 437) | async put(doc: { _id: string; data: any; _rev?: string }) {
  method get (line 440) | async get(id: string) {
  method remove (line 443) | async remove(
  method bulkDocs (line 452) | async bulkDocs(
  method allDocs (line 461) | async allDocs(key: string | string[]) {
  method postAttachment (line 464) | async postAttachment(
  method getAttachment (line 475) | async getAttachment(docId: string) {
  method getAttachmentType (line 478) | async getAttachmentType(docId: string) {
  method setItem (line 485) | async setItem(key: string, value: any) {
  method getItem (line 491) | async getItem(key: string) {
  method removeItem (line 494) | async removeItem(key: string) {
  method randomString (line 499) | randomString(length: number) {
  method bufferToBase64 (line 502) | bufferToBase64(buffer: Buffer) {
  method datetimeString (line 505) | datetimeString() {
  method base64Encode (line 508) | base64Encode(data: any) {
  method base64Decode (line 511) | base64Decode(data: string) {
  method md5 (line 514) | md5(data: string) {
  method get (line 528) | get(obj, prop) {

FILE: electron/mapi/manager/storage/index.ts
  method listPlugins (line 2) | listPlugins() {}

FILE: electron/mapi/manager/system/index.ts
  method clearCache (line 25) | async clearCache() {
  method match (line 31) | match(name: string) {
  method list (line 34) | async list() {
  method getActionCodeFunc (line 52) | getActionCodeFunc(pluginName: string, name: string) {
  method getActionBackendFunc (line 58) | getActionBackendFunc(pluginName: string, name: string) {
  method listAction (line 64) | async listAction() {

FILE: electron/mapi/manager/system/plugin/app.ts
  type ActionInfo (line 85) | type ActionInfo = {

FILE: electron/mapi/manager/system/plugin/app/type.ts
  type AppRecord (line 1) | type AppRecord = {

FILE: electron/mapi/manager/system/plugin/file.ts
  method list (line 63) | async list(): Promise<FilePluginRecord[]> {
  method update (line 75) | async update(records: FilePluginRecord[]) {

FILE: electron/mapi/manager/system/plugin/store/index.ts
  method install (line 23) | async install(
  method publish (line 122) | async publish(
  method publishInfo (line 259) | async publishInfo(
  method storeInstallingInfo (line 308) | async storeInstallingInfo(pluginName: string) {
  method _getPluginInfo (line 319) | async _getPluginInfo(root: string, configJson: any) {

FILE: electron/mapi/manager/system/plugin/system/action.ts
  function getLocalIPAddress (line 57) | function getLocalIPAddress() {

FILE: electron/mapi/manager/type.ts
  type PluginContext (line 4) | type PluginContext = (BrowserView | {}) & {
  type SearchQuery (line 12) | type SearchQuery = {

FILE: electron/mapi/manager/window/index.ts
  type OpenOptionType (line 53) | type OpenOptionType = {
  type OpenShowWindowOption (line 63) | type OpenShowWindowOption = {
  method listBrowserViews (line 104) | listBrowserViews(): BrowserView[] {
  method listDetachWindows (line 107) | listDetachWindows(): BrowserWindow[] {
  method detachWindowOperate (line 129) | async detachWindowOperate(type: "open" | "close", action: ActionRecord) {
  method _logPluginViewError (line 154) | async _logPluginViewError(view: BrowserView, plugin: PluginRecord) {
  method _pluginViewLoad (line 191) | async _pluginViewLoad(view: BrowserView, main: string) {
  method _pluginActionCodeEnd (line 206) | async _pluginActionCodeEnd() {
  method _viewCodeCallJs (line 234) | async _viewCodeCallJs(js: string) {
  method actionCodeExecute (line 239) | async actionCodeExecute(
  method openForCode (line 323) | async openForCode(
  method open (line 440) | async open(
  method subInputChange (line 643) | async subInputChange(win: BrowserWindow, keywords: string) {
  method close (line 652) | async close(
  method openMainPluginDevTools (line 693) | async openMainPluginDevTools(option?: {}) {
  method _showInMainWindow (line 724) | async _showInMainWindow(view: BrowserView, option: OpenShowWindowOption) {
  method _showInDetachWindow (line 770) | async _showInDetachWindow(view: BrowserView, option: OpenShowWindowOptio...
  method detach (line 925) | async detach(option?: {}) {
  method toggleDetachPluginAlwaysOnTop (line 947) | async toggleDetachPluginAlwaysOnTop(
  method setDetachPluginZoom (line 955) | async setDetachPluginZoom(view: BrowserView, zoom: number, option?: {}) {
  method firePluginMoreMenuClick (line 958) | async firePluginMoreMenuClick(
  method fireDetachOperateClick (line 965) | async fireDetachOperateClick(view: BrowserView, name: string, option?: {...
  method closeDetachPlugin (line 968) | async closeDetachPlugin(view: BrowserView, option?: {}) {
  method openDetachPluginDevTools (line 971) | async openDetachPluginDevTools(view: BrowserView, option?: {}) {

FILE: electron/mapi/manager/window/remoteWeb.ts
  type FileMeta (line 8) | type FileMeta = {

FILE: electron/mapi/protocol/main.ts
  method ready (line 5) | ready() {
  method queue (line 9) | async queue(url: string) {
  method runProtocol (line 13) | async runProtocol() {
  method register (line 59) | register(
  method unregister (line 68) | unregister(

FILE: electron/mapi/render.ts
  constant MAPI (line 19) | const MAPI = {
  method init (line 20) | init(env: typeof AppEnv = null) {

FILE: electron/mapi/ui/render.ts
  function domReady (line 6) | function domReady(
  method append (line 23) | append(parent: HTMLElement, child: HTMLElement) {
  method remove (line 28) | remove(parent: HTMLElement, child: HTMLElement) {
  function useLoading (line 41) | function useLoading() {

FILE: electron/mapi/util.ts
  function exposeContext (line 3) | function exposeContext(key, value) {

FILE: electron/page/index.ts
  method ready (line 27) | ready(name: string) {
  method registerWindow (line 88) | registerWindow(name: string, win: BrowserWindow) {
  method unregisterWindow (line 91) | unregisterWindow(name: string) {

FILE: electron/preload/focusany.ts
  method onPluginReady (line 88) | onPluginReady(cb: Function) {
  method onPluginExit (line 91) | onPluginExit(cb: Function) {
  method onPluginEvent (line 95) | onPluginEvent(event: PluginEvent, callback: (data: any) => void) {
  method offPluginEvent (line 120) | offPluginEvent(event: PluginEvent, callback: (data: any) => void) {
  method offPluginEventAll (line 136) | offPluginEventAll(event: PluginEvent) {
  method onMoreMenuClick (line 147) | onMoreMenuClick(callback: (data: { name: string }) => void) {
  method registerHotkey (line 151) | registerHotkey(
  method unregisterHotkeyAll (line 187) | unregisterHotkeyAll() {
  method onLog (line 191) | onLog(cb: Function) {
  method isMacOs (line 194) | isMacOs() {
  method isWindows (line 197) | isWindows() {
  method isLinux (line 200) | isLinux() {
  method getPlatformArch (line 203) | getPlatformArch() {
  method isMainWindowShown (line 206) | isMainWindowShown(): boolean {
  method hideMainWindow (line 209) | hideMainWindow() {
  method showMainWindow (line 212) | showMainWindow() {
  method isFastPanelWindowShown (line 215) | isFastPanelWindowShown() {
  method showFastPanelWindow (line 218) | showFastPanelWindow() {
  method hideFastPanelWindow (line 221) | hideFastPanelWindow() {
  method showOpenDialog (line 224) | showOpenDialog(options: {
  method showSaveDialog (line 245) | showSaveDialog(options: {
  method setExpendHeight (line 264) | setExpendHeight(height: number) {
  method setSubInput (line 267) | setSubInput(
  method removeSubInput (line 282) | removeSubInput() {
  method setSubInputValue (line 286) | setSubInputValue(text: string) {
  method subInputBlur (line 289) | subInputBlur() {
  method getPluginRoot (line 292) | getPluginRoot() {
  method getPluginConfig (line 295) | getPluginConfig() {
  method getPluginInfo (line 298) | getPluginInfo() {
  method getPluginEnv (line 301) | getPluginEnv(): "dev" | "prod" {
  method getQuery (line 304) | getQuery(requestId: string): SearchQuery {
  method getPath (line 307) | getPath(
  method showToast (line 324) | showToast(
  method showNotification (line 333) | showNotification(body: string, clickActionName?: string) {
  method showMessageBox (line 336) | showMessageBox(
  method copyImage (line 351) | copyImage(image: string) {
  method copyText (line 354) | copyText(text: string) {
  method copyFile (line 357) | copyFile(file: string | string[]) {
  method getClipboardText (line 360) | getClipboardText() {
  method getClipboardImage (line 363) | getClipboardImage() {
  method getClipboardFiles (line 366) | getClipboardFiles(): {
  method listClipboardItems (line 375) | async listClipboardItems(option?: { limit?: number }): Promise<
  method deleteClipboardItem (line 386) | async deleteClipboardItem(timestamp: number): Promise<void> {
  method clearClipboardItems (line 389) | async clearClipboardItems(): Promise<void> {
  method shellOpenExternal (line 392) | shellOpenExternal(url: string) {
  method shellOpenPath (line 395) | shellOpenPath(path: string) {
  method shellShowItemInFolder (line 398) | shellShowItemInFolder(path: string) {
  method shellBeep (line 401) | shellBeep() {
  method getFileIcon (line 404) | getFileIcon(path: string) {
  method keyboardTap (line 409) | keyboardTap(
  method typeString (line 415) | typeString(text: string) {
  method mouseToggle (line 418) | mouseToggle(type: "down" | "up", button: "left" | "right" | "middle") {
  method mouseMove (line 421) | mouseMove(x: number, y: number) {
  method mouseClick (line 424) | mouseClick(button: "left" | "right" | "middle", double?: boolean) {
  method getCursorScreenPoint (line 429) | getCursorScreenPoint() {
  method getDisplayNearestPoint (line 432) | getDisplayNearestPoint(point: { x: number; y: number }) {
  method createBrowserWindow (line 435) | createBrowserWindow(
  method screenCapture (line 487) | screenCapture(cb: (imgBase64: string) => void): void {
  method getNativeId (line 494) | getNativeId(): string {
  method getAppVersion (line 497) | getAppVersion(): string {
  method outPlugin (line 500) | outPlugin() {
  method isDarkColors (line 503) | isDarkColors() {
  method redirect (line 506) | redirect(keywordsOrAction: string | string[], query?: SearchQuery): void {
  method getActions (line 509) | getActions(names?: string[]): PluginAction[] {
  method setAction (line 512) | setAction(action: PluginAction | PluginAction[]) {
  method removeAction (line 515) | removeAction(name: string) {
  method sendBackendEvent (line 519) | sendBackendEvent(
  method registerCallPage (line 550) | registerCallPage(
  method callPage (line 576) | callPage(
  method setRemoteWebRuntime (line 586) | setRemoteWebRuntime(info: {
  method llmListModels (line 596) | llmListModels(): Promise<
  method llmChat (line 608) | llmChat(callInfo: {
  method logInfo (line 622) | logInfo(label: string, data?: any): void {
  method logError (line 626) | logError(label: string, data?: any): void {
  method logPath (line 630) | logPath(): Promise<string> {
  method logShow (line 634) | logShow(): void {
  method addLaunch (line 638) | async addLaunch(
  method removeLaunch (line 646) | async removeLaunch(keyword: string): Promise<void> {
  method activateLatestWindow (line 650) | async activateLatestWindow(): Promise<void> {
  method showUserLogin (line 654) | showUserLogin() {
  method getUser (line 658) | getUser() {
  method getUserAccessToken (line 662) | getUserAccessToken(): Promise<{ token: string; expireAt: number }> {
  method listGoods (line 666) | listGoods(query?: { ids?: string[] }): Promise<
  method openGoodsPayment (line 679) | openGoodsPayment(options: {
  method queryGoodsOrders (line 690) | queryGoodsOrders(options: {
  method apiPost (line 706) | apiPost(
  method exists (line 719) | exists(path: string): Promise<boolean> {
  method read (line 722) | read(
  method write (line 728) | write(
  method remove (line 737) | remove(path: string): Promise<void> {
  method ext (line 740) | ext(path: string): Promise<string> {
  method writeTemp (line 743) | writeTemp(
  method put (line 755) | put(doc: DbDoc) {
  method get (line 758) | get<T extends {} = Record<string, any>>(id: string): DbDoc<T> | null {
  method remove (line 761) | remove(doc: string | DbDoc): DbReturn {
  method bulkDocs (line 764) | bulkDocs(docs: DbDoc[]): DbReturn[] {
  method allDocs (line 767) | allDocs<T extends {} = Record<string, any>>(key?: string): DbDoc<T>[] {
  method postAttachment (line 770) | postAttachment(
  method getAttachment (line 781) | getAttachment(docId: string): Uint8Array | null {
  method getAttachmentType (line 784) | getAttachmentType(docId: string): string | null {
  method setItem (line 789) | setItem(key: string, value: any) {
  method getItem (line 795) | getItem(key: string) {
  method removeItem (line 798) | removeItem(key: string) {
  method setHeight (line 804) | setHeight(height: number) {
  method getHeight (line 807) | getHeight(): Promise<number> {
  method read (line 813) | async read(type: string, path: string): Promise<any> {
  method write (line 824) | async write(type: string, path: string, data: any): Promise<void> {
  method setTitle (line 835) | setTitle(title: string) {
  method setOperates (line 838) | setOperates(
  method setPosition (line 872) | setPosition(
  method setAlwaysOnTop (line 882) | setAlwaysOnTop(alwaysOnTop: boolean) {
  method setSize (line 885) | setSize(width: number, height: number) {
  method randomString (line 891) | randomString(length: number): string {
  method bufferToBase64 (line 894) | bufferToBase64(buffer: Buffer): string {
  method base64ToBuffer (line 897) | base64ToBuffer(base64: string): Buffer {
  method datetimeString (line 900) | datetimeString(): string {
  method base64Encode (line 903) | base64Encode(data: any): string {
  method base64Decode (line 906) | base64Decode(data: string): any {
  method md5 (line 909) | md5(data: string): string {
  method save (line 912) | save(

FILE: public/iconfont/iconfont.js
  function d (line 1) | function d(){h||(h=!0,o())}
  function n (line 1) | function n(){try{e.documentElement.doScroll("left")}catch(a){return void...

FILE: scripts/common.cjs
  function calcSha256File (line 18) | function calcSha256File(filePath) {
  function calcSha256 (line 108) | async function calcSha256() {

FILE: sdk/bin/command.ts
  type Config (line 7) | interface Config {
  function releasePrepare (line 16) | function releasePrepare(args: string[]) {
  function version (line 67) | function version() {
  function showHelp (line 84) | function showHelp() {
  function main (line 104) | function main() {

FILE: sdk/electron-browser-window.d.ts
  type WebPreferences (line 2) | interface WebPreferences {
  type InitOptions (line 9) | interface InitOptions {
  type NativeImage (line 28) | interface NativeImage {
  type PrinterSync (line 35) | interface PrinterSync {
  type WebRTCIPHandlingPolicy (line 47) | type WebRTCIPHandlingPolicy =
  type WebContents (line 53) | interface WebContents {
  type Rectangle (line 147) | interface Rectangle {
  type WindowInstance (line 154) | interface WindowInstance {

FILE: sdk/electron.d.ts
  type ClipboardType (line 2) | type ClipboardType = "selection" | "clipboard";
  type UIpcSendEventInit (line 51) | interface UIpcSendEventInit {
  type UIpcSendEventListener (line 55) | type UIpcSendEventListener<T extends any[]> = (event: UIpcSendEventInit,...
  type NativeImage (line 73) | type NativeImage = BrowserWindow.NativeImage;

FILE: sdk/focusany-shim.d.ts
  type FocusAnyShimType (line 1) | interface FocusAnyShimType {

FILE: sdk/focusany-shim.ts
  method init (line 4) | init() {

FILE: sdk/focusany.d.ts
  type Window (line 4) | interface Window {
  type DbDoc (line 8) | type DbDoc<T extends {} = Record<string, any>> = {
  type DbReturn (line 13) | interface DbReturn {
  type BaseResult (line 22) | type BaseResult<T = any> = {
  type PlatformType (line 28) | type PlatformType = "win" | "osx" | "linux";
  type EditionType (line 30) | type EditionType = "open" | "pro";
  type PluginEvent (line 32) | type PluginEvent = "ClipboardChange" | "UserChange";
  type HotkeyModifierType (line 34) | type HotkeyModifierType = "Control" | "Option" | "Command" | "Ctrl" | "A...
  type HotkeyType (line 36) | type HotkeyType = { key: string; modifiers: HotkeyModifierType[] };
  type HotkeyQuickType (line 38) | type HotkeyQuickType = "save";
  type ActionMatch (line 40) | type ActionMatch =
  type ActionMatchTypeEnum (line 49) | enum ActionMatchTypeEnum {
  type SearchQuery (line 59) | type SearchQuery = {
  type FileItem (line 66) | type FileItem = {
  type ActionCodeSetting (line 74) | type ActionCodeSetting = {
  type ActionCodeExecuteResultItem (line 79) | type ActionCodeExecuteResultItem = {
  type ActionCodeExecuteResult (line 89) | type ActionCodeExecuteResult = {
  type ActionMatchBase (line 101) | type ActionMatchBase = {
  type ActionMatchText (line 106) | type ActionMatchText = ActionMatchBase & {
  type ActionMatchKey (line 112) | type ActionMatchKey = ActionMatchBase & {
  type ActionMatchRegex (line 116) | type ActionMatchRegex = ActionMatchBase & {
  type ActionMatchFile (line 123) | type ActionMatchFile = ActionMatchBase & {
  type ActionMatchImage (line 131) | type ActionMatchImage = ActionMatchBase & {
  type ActionMatchWindow (line 135) | type ActionMatchWindow = ActionMatchBase & {
  type ActionMatchEditor (line 141) | type ActionMatchEditor = ActionMatchBase & {
  type PluginAction (line 146) | interface PluginAction {
  type CallPageOption (line 156) | type CallPageOption = {
  type FocusAnyApi (line 167) | interface FocusAnyApi {

FILE: src/api/types/base.ts
  type ApiResult (line 1) | interface ApiResult<T> {

FILE: src/api/user.ts
  function userInfoApi (line 3) | function userInfoApi(): Promise<

FILE: src/config.ts
  constant BASE_URL (line 3) | const BASE_URL = "https://focusany.com";

FILE: src/declarations/type.d.ts
  type DefsPage (line 1) | type DefsPage = {
  type DefsMapi (line 39) | type DefsMapi = {
  type Window (line 649) | interface Window {

FILE: src/lang/index.ts
  type LocaleItem (line 47) | type LocaleItem = {

FILE: src/lib/api.ts
  function createService (line 7) | function createService() {
  function createRequest (line 45) | function createRequest(service: AxiosInstance) {

FILE: src/lib/audio.ts
  method audioBufferEmpty (line 2) | audioBufferEmpty() {
  method audioBufferCut (line 17) | audioBufferCut(buffer: AudioBuffer, start: number, end: number) {
  method audioBufferConvert (line 38) | audioBufferConvert(
  method audioBufferToWav (line 67) | audioBufferToWav(buffer: AudioBuffer) {
  method audioBufferDuration (line 106) | audioBufferDuration(buffer: AudioBuffer) {
  method audioBufferToWavBlob (line 109) | audioBufferToWavBlob(buffer: AudioBuffer) {
  method fileToAudioBuffer (line 112) | fileToAudioBuffer(file: File) {
  method parseAudioFile (line 123) | parseAudioFile(file: File) {

FILE: src/lib/error.ts
  function mapError (line 3) | function mapError(msg: any) {

FILE: src/lib/file.ts
  method extensionToType (line 4) | extensionToType(extension: string) {
  method bufferToBlob (line 17) | bufferToBlob(buffer: ArrayBuffer, type: string) {
  method base64ToBuffer (line 23) | base64ToBuffer(base64: string) {
  method blobToFile (line 32) | blobToFile(blob: Blob, name: string) {
  method urlToBlob (line 35) | urlToBlob(url: string): Promise<Blob> {
  method blobToBase64Url (line 38) | blobToBase64Url(blob: Blob): Promise<string> {
  method getExt (line 50) | getExt(path: string) {
  method getBaseName (line 57) | getBaseName(path: string, withExt: boolean = false) {
  method md5File (line 75) | async md5File(file: File): Promise<string> {
  method md5Stream (line 118) | async md5Stream(stream: ReadableStream<Uint8Array>): Promise<string> {

FILE: src/lib/markdown.ts
  method toHtml (line 6) | toHtml(markdown: string): string {

FILE: src/lib/toggle.ts
  method gc (line 5) | gc() {
  method get (line 13) | get(biz: string, bizId: any, defaultValue: boolean = false) {
  method toggle (line 29) | toggle(biz: string, bizId: any) {

FILE: src/lib/ui.ts
  type DomListener (line 1) | type DomListener = {
  type WindowListener (line 17) | type WindowListener = {
  method onWindowResize (line 28) | onWindowResize(callback: (width: number, height: number) => void) {
  method offWindowResize (line 31) | offWindowResize(callback: (width: number, height: number) => void) {
  method onResize (line 36) | onResize(
  method offResize (line 44) | offResize(dom: HTMLElement | null) {
  method fireResize (line 49) | fireResize(dom: HTMLElement) {
  class TabContentScroller (line 80) | class TabContentScroller {
    method constructor (line 90) | constructor(
    method init (line 107) | init() {
    method destroy (line 118) | destroy() {
    method onTabClickEvent (line 129) | onTabClickEvent(e: MouseEvent) {
    method onContentScrollEvent (line 142) | onContentScrollEvent(e: Event) {
    method forceActiveTab (line 178) | forceActiveTab(name: string) {
    method scrollTo (line 191) | scrollTo(name: string) {

FILE: src/lib/util.ts
  function preciseInterval (line 28) | function preciseInterval(callback: () => void, interval: number) {
  method random (line 52) | random(length: number = 16) {
  method timestamp (line 79) | timestamp() {
  method datetimeToTimestamp (line 82) | datetimeToTimestamp(datetime: string) {
  method timestampMS (line 85) | timestampMS() {
  method format (line 88) | format(time: number, format: string = "YYYY-MM-DD HH:mm:ss") {
  method formatDate (line 91) | formatDate(time: number) {
  method dateString (line 94) | dateString() {
  method datetimeString (line 97) | datetimeString() {
  method secondsToTime (line 100) | secondsToTime(seconds: number, showMs: boolean = false) {
  method msToTime (line 118) | msToTime(ms: number) {
  method secondsToHuman (line 121) | secondsToHuman(seconds: number) {
  method replacePattern (line 132) | replacePattern(text: string) {
  method base64Encode (line 144) | base64Encode(str: string) {
  method base64Decode (line 147) | base64Decode(str: string) {
  method match (line 158) | match(v: string, match: string) {
  method compare (line 176) | compare(v1: string, v2: string) {
  method gt (line 190) | gt(v1: string, v2: string) {
  method ge (line 193) | ge(v1: string, v2: string) {
  method lt (line 196) | lt(v1: string, v2: string) {
  method isMac (line 208) | isMac() {
  method isWindows (line 211) | isWindows() {
  method isLinux (line 214) | isLinux() {
  method quotaPath (line 220) | quotaPath(p: string) {
  method clone (line 226) | clone(obj: any) {
  method downloadFile (line 232) | downloadFile(content: string, filename?: string) {

FILE: src/module/Model/models.ts
  function getModelLogo (line 146) | function getModelLogo(modelId: string) {

FILE: src/module/Model/provider/driver/base.ts
  class AbstractModelProvider (line 4) | class AbstractModelProvider {
    method constructor (line 14) | constructor(config: {
    method chat (line 25) | async chat(prompt: string, chatParam: ChatParam): Promise<ModelChatRes...

FILE: src/module/Model/provider/driver/openai.ts
  class OpenAiModelProvider (line 5) | class OpenAiModelProvider extends AbstractModelProvider {
    method constructor (line 6) | constructor(config: {
    method chat (line 17) | async chat(prompt: string, chatParam: ChatParam): Promise<ModelChatRes...

FILE: src/module/Model/provider/provider.ts
  type ModelChatResult (line 9) | type ModelChatResult = {
  method apiUrl (line 19) | apiUrl(type: ProviderType, apiUrl: string, apiHost: string = "") {
  method chat (line 42) | async chat(

FILE: src/module/Model/providers.ts
  function getProviderLogo (line 99) | function getProviderLogo(providerId: string) {
  function getProviderUrl (line 103) | function getProviderUrl(provider: Provider) {

FILE: src/module/Model/store/model.ts
  type ModelItem (line 23) | type ModelItem = {
  method state (line 76) | state() {
  method init (line 82) | async init() {
  method enabledModels (line 190) | async enabledModels(): Promise<ModelItem[]> {
  method refreshBuildIn (line 210) | async refreshBuildIn(
  method add (line 271) | async add(provider: Partial<Provider>) {
  method edit (line 294) | async edit(provider: Partial<Provider>) {
  method test (line 317) | async test(providerId: string, modelId: string) {
  method chat (line 354) | async chat(
  method change (line 402) | async change(
  method modelAdd (line 422) | async modelAdd(providerId: string, model: Partial<Model>) {
  method modelDelete (line 439) | async modelDelete(providerId: string, modelId: string) {
  method modelEdit (line 450) | async modelEdit(providerId: string, model: Partial<Model>) {
  method changeModel (line 472) | async changeModel(

FILE: src/module/Model/types.ts
  type ProviderType (line 1) | type ProviderType = "openai";
  type ModelType (line 3) | type ModelType = "text";
  type Model (line 5) | type Model = {
  type Provider (line 15) | type Provider = {
  type ChatParam (line 36) | type ChatParam = {

FILE: src/pages/Main/Lib/resultOperate.ts
  type ActionGroupType (line 11) | type ActionGroupType =

FILE: src/pages/System/components/type.ts
  type SystemDataRecord (line 3) | type SystemDataRecord = {

FILE: src/store/modules/app.ts
  method state (line 5) | state() {
  method init (line 9) | async init() {}

FILE: src/store/modules/manager.ts
  method init (line 84) | async init() {
  method setConfig (line 88) | async setConfig(key: string, value: any) {
  method onConfigChange (line 93) | async onConfigChange(key: string, value: any) {
  method configGet (line 96) | configGet(key: string, defaultValue: any = null) {
  method setActivePluginLoading (line 104) | setActivePluginLoading(loading: boolean) {
  method setActivePlugin (line 107) | setActivePlugin(
  method setSearchValue (line 114) | setSearchValue(value: string) {
  method setSelectedAction (line 120) | setSelectedAction(action: ActionRecord) {
  method setCurrentFiles (line 130) | setCurrentFiles(files: FileItem[]) {
  method setCurrentImage (line 133) | setCurrentImage(image: string) {
  method setCurrentText (line 136) | setCurrentText(text: string) {
  method searchFastPanel (line 140) | async searchFastPanel(keywords: string) {
  method searchRefresh (line 162) | async searchRefresh() {
  method search (line 166) | async search(keywords: string) {
  method detachWindowActionsRefresh (line 206) | async detachWindowActionsRefresh() {
  method resize (line 210) | async resize(width: number, height: number) {
  method isMainWindowShown (line 221) | async isMainWindowShown() {
  method showMainWindow (line 224) | async showMainWindow() {
  method hideMainWindow (line 227) | async hideMainWindow() {
  method openAction (line 230) | async openAction(
  method openActionCode (line 249) | async openActionCode(id: string) {
  method openActionWindow (line 256) | async openActionWindow(type: "open", action: ActionRecord) {
  method closeMainPlugin (line 259) | async closeMainPlugin() {
  method openMainPluginDevTools (line 262) | async openMainPluginDevTools() {
  method openMainPluginLog (line 265) | async openMainPluginLog() {
  method detachPlugin (line 268) | async detachPlugin() {
  method setSubInput (line 271) | setSubInput(payload: {
  method removeSubInput (line 282) | removeSubInput() {
  method setSubInputValue (line 290) | setSubInputValue(value: string) {
  method onNotice (line 296) | onNotice(data: any) {

FILE: src/store/modules/setting.ts
  method state (line 9) | state() {
  method init (line 25) | async init() {
  method showGuideWhenReady (line 35) | async showGuideWhenReady() {
  method onConfigChangeBroadcast (line 49) | onConfigChangeBroadcast(data: any) {
  method onConfigEnvChangeBroadcast (line 58) | onConfigEnvChangeBroadcast(data: any) {
  method onDarkModeChangeBroadcast (line 63) | onDarkModeChangeBroadcast(data: any) {
  method shouldDarkMode (line 67) | shouldDarkMode() {
  method setupDarkMode (line 78) | setupDarkMode() {
  method initBasic (line 90) | async initBasic(basic: object) {
  method setConfig (line 93) | async setConfig(key: string, value: any) {
  method setConfigEnv (line 101) | async setConfigEnv(key: string, value: any) {
  method onConfigChange (line 105) | async onConfigChange(key: string, value: any) {
  method onConfigEnvChange (line 108) | async onConfigEnvChange(key: string, value: any) {
  method configGet (line 111) | configGet(key: string, defaultValue: any = null) {
  method configEnvGet (line 119) | configEnvGet(key: string, defaultValue: any = null) {

FILE: src/store/modules/task.ts
  type TaskRecordStatus (line 24) | type TaskRecordStatus =
  type TaskRecordRunStatus (line 32) | type TaskRecordRunStatus = "retry" | "success" | "querying";
  type TaskRecordQueryStatus (line 34) | type TaskRecordQueryStatus = "running" | "success" | "fail";
  type TaskChangeType (line 36) | type TaskChangeType =
  type TaskRecord (line 43) | type TaskRecord = {
  type TaskBiz (line 68) | type TaskBiz = {
  method queryFunc (line 113) | queryFunc(bizId, bizParam) {
  method state (line 130) | state() {
  method init (line 144) | async init() {
  method waitInit (line 151) | async waitInit() {
  method _runExecute (line 156) | _runExecute() {
  method _run (line 357) | _run(immediate: boolean) {
  method get (line 369) | get(biz: string) {
  method register (line 372) | register(biz: string, taskBiz: TaskBiz) {
  method unregister (line 375) | unregister(biz: string) {
  method onChange (line 378) | onChange(
  method offChange (line 384) | offChange(
  method fireChange (line 393) | fireChange(record: Partial<TaskRecord>, type: TaskChangeType) {
  method requestCancel (line 400) | requestCancel(biz: string, bizId: string) {
  method shouldCancel (line 413) | shouldCancel(biz: string, bizId: string) {
  method dispatch (line 426) | async dispatch(
  method sync (line 464) | async sync() {

FILE: src/store/modules/user.ts
  method state (line 9) | state() {
  method init (line 37) | async init() {
  method load (line 40) | async load() {
  method onChangeBroadcast (line 50) | onChangeBroadcast() {
  method waitInit (line 53) | async waitInit() {
  method webUrl (line 66) | async webUrl() {

FILE: src/task/index.ts
  method init (line 7) | init() {
  method count (line 27) | count() {

FILE: src/types/Manager.ts
  type ConfigRecord (line 6) | type ConfigRecord = {
  type PluginConfig (line 12) | type PluginConfig = {
  type PluginType (line 17) | enum PluginType {
  type PluginEnv (line 24) | enum PluginEnv {
  type PluginPermissionType (line 29) | type PluginPermissionType = "ClipboardManage" | "Api" | "File" | never;
  type PluginRecord (line 31) | type PluginRecord = {
  type PluginState (line 102) | type PluginState = {
  type ActionMatch (line 108) | type ActionMatch =
  type ActionMatchTypeEnum (line 117) | enum ActionMatchTypeEnum {
  type ActionMatchBase (line 127) | type ActionMatchBase = {
  type ActionMatchText (line 132) | type ActionMatchText = ActionMatchBase & {
  type ActionMatchKey (line 138) | type ActionMatchKey = ActionMatchBase & {
  type ActionMatchRegex (line 142) | type ActionMatchRegex = ActionMatchBase & {
  type ActionMatchFile (line 149) | type ActionMatchFile = ActionMatchBase & {
  type ActionMatchImage (line 157) | type ActionMatchImage = ActionMatchBase & {
  type ActionMatchWindow (line 161) | type ActionMatchWindow = ActionMatchBase & {
  type ActionMatchEditor (line 167) | type ActionMatchEditor = ActionMatchBase & {
  type SelectedContent (line 172) | type SelectedContent = {
  type ActiveWindow (line 179) | type ActiveWindow = {
  type ClipboardDataType (line 186) | type ClipboardDataType = {
  type ClipboardHistoryRecord (line 193) | type ClipboardHistoryRecord = {
  type ActionRecord (line 201) | type ActionRecord = {
  type MCPToolsRecord (line 240) | type MCPToolsRecord = {
  type PluginActionRecord (line 253) | type PluginActionRecord = {
  type ActionTypeCodeData (line 258) | type ActionTypeCodeData = {
  type ActionTypeEnum (line 262) | enum ActionTypeEnum {
  type FilePluginRecord (line 270) | type FilePluginRecord = {
  type LaunchRecord (line 276) | type LaunchRecord = {

FILE: src/vite-env.d.ts
  type ComponentCustomProperties (line 13) | interface ComponentCustomProperties {

FILE: vite.config.ts
  method generateBundle (line 52) | generateBundle() {
  method closeBundle (line 69) | closeBundle() {
  method onstart (line 98) | onstart({startup}) {
  method onstart (line 124) | onstart({reload}) {
  method onstart (line 154) | onstart({reload}) {
Condensed preview — 368 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,694K chars).
[
  {
    "path": ".editorconfig",
    "chars": 256,
    "preview": "root = true\n\n[*]\ncharset = utf-8\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 4\ntrim_"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "chars": 159,
    "preview": "---\n\nname: 🐞 Bug report\nabout: Create a report to help us improve\ntitle: \"[Bug] the title of bug report\"\nlabels: bug\nass"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/help_wanted.md",
    "chars": 200,
    "preview": "---\nname: 🥺 Help wanted\nabout: Confuse about the use of electron-vue-vite\ntitle: \"[Help] the title of help wanted report"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 321,
    "preview": "<!-- Thank you for contributing! -->\n\n### Description\n\n<!-- Please insert your description here and provide especially i"
  },
  {
    "path": ".github/workflows/build.yml",
    "chars": 7697,
    "preview": "name: Build\n\non:\n  push:\n    tags:\n      - v*.*.*\n    branches:\n      - main\n  workflow_dispatch:\n\njobs:\n  build:\n    ru"
  },
  {
    "path": ".github/workflows/main-build.yml",
    "chars": 5789,
    "preview": "name: MainBuild\n\non:\n    push:\n        branches:\n            - mainx\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n\n    "
  },
  {
    "path": ".github/workflows/tag-release.yml",
    "chars": 4308,
    "preview": "name: TagRelease\n\non:\n    push:\n        tags:\n            - v*.*.*\n\njobs:\n  build:\n    runs-on: ${{ matrix.os }}\n\n    st"
  },
  {
    "path": ".gitignore",
    "chars": 440,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n/dist\n/d"
  },
  {
    "path": ".npmrc",
    "chars": 310,
    "preview": "# For electron-builder\n# https://github.com/electron-userland/electron-builder/issues/6289#issuecomment-1042620422\nshame"
  },
  {
    "path": ".nvmrc",
    "chars": 3,
    "preview": "20\n"
  },
  {
    "path": "LICENSE",
    "chars": 9136,
    "preview": "Apache License\nVersion 2.0, January 2004\nhttp://www.apache.org/licenses/\n\nTERMS AND CONDITIONS FOR USE, REPRODUCTION, AN"
  },
  {
    "path": "Makefile",
    "chars": 2480,
    "preview": "\n\n# make test              → 完整测试套件(构建验证)\n# make test biz          → 直接运行全部 test/biz/*.test.ts(需已启动 Electron)\n# make tes"
  },
  {
    "path": "README.md",
    "chars": 5587,
    "preview": "# 🎯 FocusAny - 智能AI办公助理\n\n<div align=\"center\">\n  <img src=\"./screenshots/cn/home.png\" alt=\"FocusAny 主界面\" width=\"800\"/>\n</"
  },
  {
    "path": "SECURITY.md",
    "chars": 1905,
    "preview": "# Security Policy\n\n## Supported Versions\n\nWe strongly recommend always using the latest version to benefit from the late"
  },
  {
    "path": "cli/cmd/plugin.go",
    "chars": 582,
    "preview": "package cmd\n\nimport (\n\t\"focusany-cli/internal\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar pluginCmd = &cobra.Command{\n\tUse:   \"pl"
  },
  {
    "path": "cli/cmd/root.go",
    "chars": 494,
    "preview": "package cmd\n\nimport (\n\t\"os\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar appVersion string\n\n// Execute sets the version and runs th"
  },
  {
    "path": "cli/cmd/version.go",
    "chars": 305,
    "preview": "package cmd\n\nimport (\n\t\"focusany-cli/internal\"\n\n\t\"github.com/spf13/cobra\"\n)\n\nvar versionCmd = &cobra.Command{\n\tUse:   \"v"
  },
  {
    "path": "cli/go.mod",
    "chars": 181,
    "preview": "module focusany-cli\n\ngo 1.22\n\nrequire github.com/spf13/cobra v1.8.0\n\nrequire (\n\tgithub.com/inconshreveable/mousetrap v1."
  },
  {
    "path": "cli/go.sum",
    "chars": 896,
    "preview": "github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=\ngithub.com/inconshreveabl"
  },
  {
    "path": "cli/internal/client.go",
    "chars": 1537,
    "preview": "package internal\n\nimport (\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n)\n\n// DoRequest sends an HTTP request to t"
  },
  {
    "path": "cli/internal/config.go",
    "chars": 1823,
    "preview": "package internal\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"runtime\"\n)\n\n// AuthConfig holds the port and"
  },
  {
    "path": "cli/main.go",
    "chars": 172,
    "preview": "package main\n\nimport \"focusany-cli/cmd\"\n\n// Version is injected at build time via ldflags: -X main.Version=x.x.x\nvar Ver"
  },
  {
    "path": "electron/config/common.ts",
    "chars": 429,
    "preview": "export const CommonConfig = {\n    darkModeEnable: true,\n    dbSystem: \"system\",\n    dbConfigId: \"config\",\n    dbDisabled"
  },
  {
    "path": "electron/config/contextMenu.ts",
    "chars": 378,
    "preview": "import contextMenu from \"electron-context-menu\";\n\nconst init = () => {\n    contextMenu({\n        showSaveImageAs: false,"
  },
  {
    "path": "electron/config/icon.ts",
    "chars": 375,
    "preview": "import { buildResolve, extraResolve } from \"../lib/env\";\n\nexport const logoPath = buildResolve(\"logo.png\");\nexport const"
  },
  {
    "path": "electron/config/lang.ts",
    "chars": 1508,
    "preview": "import enUS from \"./../../src/lang/en-US.json\";\nimport zhCN from \"./../../src/lang/zh-CN.json\";\nimport { isDev } from \"."
  },
  {
    "path": "electron/config/menu.ts",
    "chars": 3440,
    "preview": "import { app, Menu } from \"electron\";\nimport { isDev, isMac } from \"../lib/env\";\nimport { t } from \"./lang\";\n\nlet contex"
  },
  {
    "path": "electron/config/tray.ts",
    "chars": 1964,
    "preview": "import { app, Menu, shell, Tray } from \"electron\";\nimport { trayPath } from \"./icon\";\nimport { AppRuntime } from \"../map"
  },
  {
    "path": "electron/config/window.ts",
    "chars": 598,
    "preview": "export const WindowConfig = {\n    alwaysOpenDevTools: true,\n    minWidth: 800,\n    minHeight: 60,\n    initWidth: 800,\n  "
  },
  {
    "path": "electron/declarations/electron.d.ts",
    "chars": 233,
    "preview": "declare module \"electron\" {\n    interface BrowserView {\n        _window?: any;\n        _plugin?: any;\n    }\n\n    interfa"
  },
  {
    "path": "electron/declarations/svg.d.ts",
    "chars": 82,
    "preview": "declare module \"*.svg\" {\n    const content: string;\n    export default content;\n}\n"
  },
  {
    "path": "electron/electron-env.d.ts",
    "chars": 607,
    "preview": "/// <reference types=\"vite-plugin-electron/electron-env\" />\n/// <reference types=\"../sdk/focusany\" />\n\ndeclare namespace"
  },
  {
    "path": "electron/lib/api.ts",
    "chars": 631,
    "preview": "import Apps from \"../mapi/app\";\n\nexport type ResultType<T> = {\n    // should follow the rules:\n    // <0 business error\n"
  },
  {
    "path": "electron/lib/devtools.ts",
    "chars": 3472,
    "preview": "import { BrowserView, BrowserWindow, screen } from \"electron\";\nimport { isDev } from \"./env\";\nimport { WindowConfig } fr"
  },
  {
    "path": "electron/lib/env-main.ts",
    "chars": 2619,
    "preview": "import url, { fileURLToPath } from \"node:url\";\nimport { BrowserView, BrowserWindow } from \"electron\";\nimport { isPackage"
  },
  {
    "path": "electron/lib/env.ts",
    "chars": 5041,
    "preview": "import { execSync } from \"child_process\";\nimport { resolve } from \"node:path\";\nimport fs from \"node:fs\";\nimport os from "
  },
  {
    "path": "electron/lib/hooks.ts",
    "chars": 551,
    "preview": "import { BrowserWindow } from \"electron\";\n\ntype HookType = never | \"Show\" | \"Hide\";\n\nexport const executeHooks = async ("
  },
  {
    "path": "electron/lib/permission.ts",
    "chars": 1293,
    "preview": "import { isMac } from \"./env\";\n\nlet nodeMacPermissions = null;\nif (isMac) {\n    (async () => {\n        try {\n           "
  },
  {
    "path": "electron/lib/pinyin-util.ts",
    "chars": 808,
    "preview": "import PinyinMatch from \"pinyin-match\";\n\nexport const PinyinUtil = {\n    match(input, keywords) {\n        const index = "
  },
  {
    "path": "electron/lib/process.ts",
    "chars": 399,
    "preview": "/** 在主进程中获取关键信息存储到环境变量中,从而在预加载脚本中及渲染进程中使用 */\nimport { app } from \"electron\";\n\n/** 注意: app.isPackaged 可能被被某些方法改变所以请将该文件放到"
  },
  {
    "path": "electron/lib/util.ts",
    "chars": 29248,
    "preview": "import chardet from \"chardet\";\nimport dayjs from \"dayjs\";\nimport iconvLite from \"iconv-lite\";\nimport { Base64 } from \"js"
  },
  {
    "path": "electron/main/fastPanel.ts",
    "chars": 3406,
    "preview": "import { icnsLogoPath, icoLogoPath, logoPath } from \"../config/icon\";\nimport { AppRuntime } from \"../mapi/env\";\nimport {"
  },
  {
    "path": "electron/main/index.ts",
    "chars": 8909,
    "preview": "import { app, BrowserWindow, desktopCapturer, session, shell } from \"electron\";\nimport { optimizer } from \"@electron-too"
  },
  {
    "path": "electron/mapi/app/icons.ts",
    "chars": 4402,
    "preview": "export const icons = {\n    success:\n        '<svg t=\"1733817409678\" class=\"icon\" viewBox=\"0 0 1024 1024\" version=\"1.1\" x"
  },
  {
    "path": "electron/mapi/app/index.ts",
    "chars": 10641,
    "preview": "import iconv from \"iconv-lite\";\nimport { exec as _exec, spawn } from \"node:child_process\";\nimport net from \"node:net\";\ni"
  },
  {
    "path": "electron/mapi/app/lib/position.ts",
    "chars": 2964,
    "preview": "import { screen } from \"electron\";\n\ntype PositionCache = {\n    x: 0;\n    y: 0;\n    screenWidth: 0;\n    screenHeight: 0;\n"
  },
  {
    "path": "electron/mapi/app/loading.ts",
    "chars": 4862,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { AppsMain } from \"./main\";\nimport { icons } from \"./icons\";\n\nexport co"
  },
  {
    "path": "electron/mapi/app/main.ts",
    "chars": 10692,
    "preview": "import {\n    app,\n    BrowserWindow,\n    clipboard,\n    ipcMain,\n    nativeImage,\n    nativeTheme,\n    screen,\n    shell"
  },
  {
    "path": "electron/mapi/app/render.ts",
    "chars": 4681,
    "preview": "import { ipcRenderer } from \"electron\";\nimport { resolve } from \"node:path\";\nimport { isPackaged, platformArch, platform"
  },
  {
    "path": "electron/mapi/app/setup.ts",
    "chars": 1775,
    "preview": "import { Permissions } from \"../../lib/permission\";\nimport { rendererDistPath } from \"../../lib/env-main\";\n\nexport const"
  },
  {
    "path": "electron/mapi/app/toast.ts",
    "chars": 5404,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { icons } from \"./icons\";\nimport { AppsMain } from \"./main\";\n\nlet win ="
  },
  {
    "path": "electron/mapi/config/index.ts",
    "chars": 844,
    "preview": "import { callHandleFromMainOrRender } from \"../env\";\n\nconst all = async () => {\n    return callHandleFromMainOrRender(\"c"
  },
  {
    "path": "electron/mapi/config/main.ts",
    "chars": 2478,
    "preview": "import path from \"node:path\";\nimport { AppEnv } from \"../env\";\nimport fs from \"node:fs\";\nimport { ipcMain } from \"electr"
  },
  {
    "path": "electron/mapi/config/render.ts",
    "chars": 774,
    "preview": "import { ipcRenderer } from \"electron\";\n\nconst all = async () => {\n    return ipcRenderer.invoke(\"config:all\");\n};\n\ncons"
  },
  {
    "path": "electron/mapi/db/db.ts",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "electron/mapi/db/main.ts",
    "chars": 5119,
    "preview": "import sqlite3, { Database } from \"better-sqlite3\";\nimport path from \"node:path\";\nimport migration from \"./migration\";\ni"
  },
  {
    "path": "electron/mapi/db/migration.ts",
    "chars": 1171,
    "preview": "const versions = [\n    {\n        version: 0,\n        up: async (db: DB) => {\n            // await db.execute(`CREATE TAB"
  },
  {
    "path": "electron/mapi/db/render.ts",
    "chars": 891,
    "preview": "import { ipcRenderer } from \"electron\";\n\nconst init = () => {};\n\nconst execute = async (sql: string, params: any = []) ="
  },
  {
    "path": "electron/mapi/db/type.d.ts",
    "chars": 333,
    "preview": "type DB = {\n    execute(sql: string, params?: any): Promise<any>;\n    insert(sql: string, params?: any): Promise<any>;\n "
  },
  {
    "path": "electron/mapi/env.ts",
    "chars": 1100,
    "preview": "import electron, { BrowserWindow } from \"electron\";\nimport { Log } from \"./log\";\n\nexport const AppEnv = {\n    isInit: fa"
  },
  {
    "path": "electron/mapi/event/main.ts",
    "chars": 6397,
    "preview": "import { AppRuntime } from \"../env\";\nimport { ipcMain, WebContents } from \"electron\";\nimport { StrUtil } from \"../../lib"
  },
  {
    "path": "electron/mapi/event/render.ts",
    "chars": 564,
    "preview": "import { ipcRenderer } from \"electron\";\n\nconst init = () => {};\n\nconst send = (name: string, type: string, data: any = {"
  },
  {
    "path": "electron/mapi/file/index.ts",
    "chars": 32831,
    "preview": "import fs, { createWriteStream } from \"node:fs\";\nimport path from \"node:path\";\nimport { Readable } from \"node:stream\";\ni"
  },
  {
    "path": "electron/mapi/file/main.ts",
    "chars": 2174,
    "preview": "import { dialog, ipcMain } from \"electron\";\nimport fileIndex from \"./index\";\n\nipcMain.handle(\n    \"file:openFile\",\n    a"
  },
  {
    "path": "electron/mapi/file/render.ts",
    "chars": 493,
    "preview": "import fileIndex from \"./index\";\nimport { ipcRenderer } from \"electron\";\n\nconst openFile = async (options: {} = {}) => {"
  },
  {
    "path": "electron/mapi/httpserver/main.ts",
    "chars": 3868,
    "preview": "import type { Request, Response } from \"express\";\nimport express from \"express\";\nimport crypto from \"node:crypto\";\nimpor"
  },
  {
    "path": "electron/mapi/keys/main.ts",
    "chars": 2453,
    "preview": "import { app, BrowserWindow, globalShortcut } from \"electron\";\nimport { AppsMain } from \"../app/main\";\nimport { ManagerH"
  },
  {
    "path": "electron/mapi/keys/type.ts",
    "chars": 518,
    "preview": "export enum HotkeyMouseButtonEnum {\n    LEFT = 1,\n    RIGHT = 2,\n}\n\nexport type HotkeyKeyItem = {\n    key: string;\n    /"
  },
  {
    "path": "electron/mapi/kvdb/kvdb.ts",
    "chars": 16128,
    "preview": "import path from \"path\";\nimport fs from \"fs\";\nimport PouchDB from \"pouchdb\";\nimport { DBError, Doc, DocRes } from \"./typ"
  },
  {
    "path": "electron/mapi/kvdb/main.ts",
    "chars": 7507,
    "preview": "import KVDB from \"./kvdb\";\nimport { AppEnv } from \"../env\";\nimport { DBError, Doc } from \"./types\";\nimport { ipcMain } f"
  },
  {
    "path": "electron/mapi/kvdb/render.ts",
    "chars": 2839,
    "preview": "import { Doc } from \"./types\";\nimport { ipcRenderer } from \"electron\";\n\nconst put = async (name: string, doc: Doc) => {\n"
  },
  {
    "path": "electron/mapi/kvdb/types.ts",
    "chars": 662,
    "preview": "type RevisionId = string;\n\nexport type Doc<T extends {} = Record<string, any>> = {\n    _id: string;\n    _rev?: string;\n "
  },
  {
    "path": "electron/mapi/kvdb/version.ts",
    "chars": 1972,
    "preview": "import DBMain from \"../db/main\";\nimport { StrUtil } from \"../../lib/util\";\n\nexport const KVDBVersionManager = {\n    asyn"
  },
  {
    "path": "electron/mapi/kvdb/webdav.ts",
    "chars": 1910,
    "preview": "import MemoryStream from \"memorystream\";\n\nimport { AuthType, createClient } from \"webdav\";\nimport { WebDAVClient } from "
  },
  {
    "path": "electron/mapi/log/beacon-render.ts",
    "chars": 1718,
    "preview": "/**\n * 渲染进程异常上报(HTTP Beacon)\n * 仅在 isPackaged(非开发)模式下上报,批量异步发送。\n */\nimport { AppConfig } from \"../../../src/config\";\n\nde"
  },
  {
    "path": "electron/mapi/log/beacon.ts",
    "chars": 1833,
    "preview": "/**\n * 主进程异常上报(HTTP Beacon)\n * 仅在 isPackaged(非开发)模式下上报,批量异步发送。\n */\nimport https from \"node:https\";\nimport { AppConfig } "
  },
  {
    "path": "electron/mapi/log/index.ts",
    "chars": 9071,
    "preview": "import electron from \"electron\";\nimport date from \"date-and-time\";\nimport path from \"node:path\";\nimport { AppEnv } from "
  },
  {
    "path": "electron/mapi/log/main.ts",
    "chars": 937,
    "preview": "import { ipcMain } from \"electron\";\nimport logIndex from \"./index\";\n\nipcMain.handle(\"log:info\", (event, label: string, d"
  },
  {
    "path": "electron/mapi/log/render.ts",
    "chars": 285,
    "preview": "import logIndex from \"./index\";\n\nexport default {\n    root: logIndex.root,\n    info: logIndex.infoRenderOrMain,\n    erro"
  },
  {
    "path": "electron/mapi/main.ts",
    "chars": 1163,
    "preview": "import app from \"./app/main\";\nimport config from \"./config/main\";\nimport db from \"./db/main\";\nimport event from \"./event"
  },
  {
    "path": "electron/mapi/manager/automation/index.ts",
    "chars": 5590,
    "preview": "import { Button, Key, keyboard, mouse } from \"@nut-tree-fork/nut-js\";\nimport { activeWindow, Result } from \"get-windows\""
  },
  {
    "path": "electron/mapi/manager/backend/index.ts",
    "chars": 2800,
    "preview": "import { ActionRecord, PluginRecord } from \"../../../../src/types/Manager\";\nimport { ManagerSystem } from \"../system\";\ni"
  },
  {
    "path": "electron/mapi/manager/clipboard/clipboardFiles.ts",
    "chars": 2746,
    "preview": "import { clipboard } from \"electron\";\nimport plist from \"plist\";\nimport fs from \"fs\";\nimport path from \"path\";\nimport of"
  },
  {
    "path": "electron/mapi/manager/clipboard/index.ts",
    "chars": 9677,
    "preview": "import { AppsMain } from \"../../app/main\";\nimport {\n    ClipboardDataType,\n    ClipboardHistoryRecord,\n} from \"../../../"
  },
  {
    "path": "electron/mapi/manager/code/index.ts",
    "chars": 1072,
    "preview": "import { ActionRecord, PluginRecord } from \"../../../../src/types/Manager\";\nimport { ManagerSystem } from \"../system\";\ni"
  },
  {
    "path": "electron/mapi/manager/config/config.ts",
    "chars": 13785,
    "preview": "import {\n    ActionRecord,\n    ConfigRecord,\n    LaunchRecord,\n    PluginActionRecord,\n    PluginConfig,\n    PluginRecor"
  },
  {
    "path": "electron/mapi/manager/editor/index.ts",
    "chars": 3655,
    "preview": "import nodePath from \"node:path\";\nimport { t } from \"../../../config/lang\";\nimport { AppsMain } from \"../../app/main\";\ni"
  },
  {
    "path": "electron/mapi/manager/hotkey/handle.ts",
    "chars": 1312,
    "preview": "import { ManagerPluginEvent } from \"../plugin/event\";\nimport { ManagerConfig } from \"../config/config\";\nimport ConfigMai"
  },
  {
    "path": "electron/mapi/manager/hotkey/index.ts",
    "chars": 12425,
    "preview": "import { uIOhook, UiohookKey } from \"uiohook-napi\";\nimport { ManagerConfig } from \"../config/config\";\nimport { ManagerHo"
  },
  {
    "path": "electron/mapi/manager/hotkey/simulate.ts",
    "chars": 335,
    "preview": "import { uIOhook, UiohookKey } from \"uiohook-napi\";\n\nexport const KeyboardKey = {\n    ...UiohookKey,\n};\n\nexport const Ma"
  },
  {
    "path": "electron/mapi/manager/lib/cache.ts",
    "chars": 2428,
    "preview": "import { Files } from \"../../file/main\";\nimport { Log } from \"../../log/main\";\n\nexport const ManagerFileCacheUtil = {\n  "
  },
  {
    "path": "electron/mapi/manager/lib/hooks.ts",
    "chars": 2542,
    "preview": "import { BrowserView, BrowserWindow } from \"electron\";\nimport { AppsMain } from \"../../app/main\";\nimport { PluginRecord "
  },
  {
    "path": "electron/mapi/manager/main.ts",
    "chars": 22756,
    "preview": "import { BrowserWindow, ipcMain } from \"electron\";\nimport {\n    ActionRecord,\n    ActionTypeEnum,\n    FilePluginRecord,\n"
  },
  {
    "path": "electron/mapi/manager/manager.ts",
    "chars": 20258,
    "preview": "import {\n    ActionMatchFile,\n    ActionMatchKey,\n    ActionMatchRegex,\n    ActionMatchText,\n    ActionMatchTypeEnum,\n  "
  },
  {
    "path": "electron/mapi/manager/plugin/colorPicker.ts",
    "chars": 17219,
    "preview": "import { FileType, screen as nutScreen, Region } from \"@nut-tree-fork/nut-js\";\nimport { BrowserWindow, ipcMain, nativeIm"
  },
  {
    "path": "electron/mapi/manager/plugin/event.ts",
    "chars": 38102,
    "preview": "import {\n    app,\n    BrowserView,\n    BrowserWindow,\n    clipboard,\n    dialog,\n    nativeImage,\n    Notification,\n    "
  },
  {
    "path": "electron/mapi/manager/plugin/http.ts",
    "chars": 2650,
    "preview": "import express from \"express\";\nimport Apps from \"../../app\";\nimport { Log } from \"../../log/main\";\nimport { ManagerPlugi"
  },
  {
    "path": "electron/mapi/manager/plugin/httpMCP.ts",
    "chars": 6570,
    "preview": "import { ManagerPlugin } from \"./index\";\nimport { Log } from \"../../log/main\";\nimport { MCPToolsRecord } from \"../../../"
  },
  {
    "path": "electron/mapi/manager/plugin/index.ts",
    "chars": 22239,
    "preview": "import {\n    ActionMatch,\n    ActionMatchKey,\n    ActionMatchRegex,\n    ActionMatchText,\n    ActionMatchTypeEnum,\n    Ac"
  },
  {
    "path": "electron/mapi/manager/plugin/llm.ts",
    "chars": 7612,
    "preview": "// @ts-ignore\nimport { Model, Provider } from \"../../../../src/module/Model/types\";\n// @ts-ignore\nimport {\n    getProvid"
  },
  {
    "path": "electron/mapi/manager/plugin/log.ts",
    "chars": 829,
    "preview": "import { t } from \"../../../config/lang\";\nimport { AppsMain } from \"../../app/main\";\nimport { Log } from \"../../log/main"
  },
  {
    "path": "electron/mapi/manager/plugin/permission.ts",
    "chars": 1586,
    "preview": "import {\n    PluginPermissionType,\n    PluginRecord,\n} from \"../../../../src/types/Manager\";\nimport { t } from \"../../.."
  },
  {
    "path": "electron/mapi/manager/plugin/screenCapture.ts",
    "chars": 1212,
    "preview": "import { exec, execFile } from \"child_process\";\nimport { clipboard, Notification } from \"electron\";\nimport { t } from \"."
  },
  {
    "path": "electron/mapi/manager/plugin/screenRecord.ts",
    "chars": 6994,
    "preview": "import { spawn } from \"child_process\";\nimport { BrowserWindow, desktopCapturer, dialog, screen } from \"electron\";\nimport"
  },
  {
    "path": "electron/mapi/manager/plugin/sdk.ts",
    "chars": 19001,
    "preview": "import { BrowserWindow, screen, shell } from \"electron\";\nimport os from \"os\";\nimport path from \"path\";\nimport { PluginRe"
  },
  {
    "path": "electron/mapi/manager/render.ts",
    "chars": 9702,
    "preview": "import { ipcRenderer } from \"electron\";\nimport {\n    ActionRecord,\n    ConfigRecord,\n    PluginRecord,\n} from \"../../../"
  },
  {
    "path": "electron/mapi/manager/storage/index.ts",
    "chars": 57,
    "preview": "export const ManagerStorage = {\n    listPlugins() {},\n};\n"
  },
  {
    "path": "electron/mapi/manager/system/asset/icon.ts",
    "chars": 1353,
    "preview": "import pluginSystem from \"./plugin-system.svg\";\nimport pluginStore from \"./plugin-store.svg\";\nimport pluginWorkflow from"
  },
  {
    "path": "electron/mapi/manager/system/index.ts",
    "chars": 2381,
    "preview": "import {\n    ActionRecord,\n    PluginRecord,\n    PluginType,\n} from \"../../../../src/types/Manager\";\nimport { SystemPlug"
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/linux/icon.ts",
    "chars": 997,
    "preview": "import path from \"node:path\";\nimport fs from \"node:fs\";\n\nexport const getIcon = async (\n    desktopInfo: Record<string, "
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/linux/index.ts",
    "chars": 2318,
    "preview": "import { listFiles } from \"../util\";\nimport path from \"path\";\nimport { ConfigLang } from \"../../../../../../config/lang\""
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/linux/title.ts",
    "chars": 514,
    "preview": "const langDirMap = {\n    \"zh-CN\": [\"zh_CN\"],\n};\n\nexport const getAppTitle = async (\n    desktopInfo: Record<string, stri"
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/mac/icon.ts",
    "chars": 3855,
    "preview": "import path from \"node:path\";\nimport fs from \"fs\";\nimport { exec } from \"child_process\";\nimport { Files } from \"../../.."
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/mac/index.ts",
    "chars": 1511,
    "preview": "import { listFiles } from \"../util\";\nimport path from \"node:path\";\nimport { AppRecord } from \"../type\";\nimport { getIcon"
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/mac/title.ts",
    "chars": 1567,
    "preview": "import { Files } from \"../../../../../file/main\";\nimport { IconvUtil } from \"../../../../../../lib/util\";\n\nconst langDir"
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/type.ts",
    "chars": 127,
    "preview": "export type AppRecord = {\n    name: string;\n    title: string;\n    pathname: string;\n    icon: string;\n    command: stri"
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/util/index.ts",
    "chars": 489,
    "preview": "import { Files } from \"../../../../../file/main\";\n\nexport const listFiles = async (\n    paths: string[],\n): Promise<\n   "
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/win/icon.ts",
    "chars": 1283,
    "preview": "import fs from \"fs\";\nimport path from \"path\";\n\nimport extractFileIcon from \"extract-file-icon\";\nimport { AppEnv, waitApp"
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/win/index.ts",
    "chars": 2201,
    "preview": "import { listFiles } from \"../util\";\nimport path from \"path\";\nimport os from \"os\";\nimport { shell } from \"electron\";\nimp"
  },
  {
    "path": "electron/mapi/manager/system/plugin/app/win/title.ts",
    "chars": 1371,
    "preview": "import { exec } from \"child_process\";\nimport { IconvUtil } from \"../../../../../../lib/util\";\n\nexport const getAppTitle "
  },
  {
    "path": "electron/mapi/manager/system/plugin/app.ts",
    "chars": 4057,
    "preview": "import {\n    ActionMatch,\n    ActionMatchTypeEnum,\n    ActionRecord,\n    ActionTypeEnum,\n    PluginRecord,\n} from \"../.."
  },
  {
    "path": "electron/mapi/manager/system/plugin/file.ts",
    "chars": 2654,
    "preview": "import {\n    ActionMatch,\n    ActionMatchTypeEnum,\n    ActionRecord,\n    ActionTypeEnum,\n    FilePluginRecord,\n    Plugi"
  },
  {
    "path": "electron/mapi/manager/system/plugin/store/action.ts",
    "chars": 222,
    "preview": "import { ActionTypeCodeData } from \"../../../../../../src/types/Manager\";\nimport { screenCapture } from \"../../../plugin"
  },
  {
    "path": "electron/mapi/manager/system/plugin/store/index.ts",
    "chars": 12862,
    "preview": "import { PluginType } from \"../../../../../../src/types/Manager\";\nimport { Files } from \"../../../../file/main\";\nimport "
  },
  {
    "path": "electron/mapi/manager/system/plugin/store.ts",
    "chars": 677,
    "preview": "import { ActionTypeEnum, PluginRecord } from \"../../../../../src/types/Manager\";\nimport { t } from \"../../../../config/l"
  },
  {
    "path": "electron/mapi/manager/system/plugin/system/action.ts",
    "chars": 2473,
    "preview": "import os from \"os\";\nimport { ActionTypeCodeData } from \"../../../../../../src/types/Manager\";\nimport { t } from \"../../"
  },
  {
    "path": "electron/mapi/manager/system/plugin/system.ts",
    "chars": 3530,
    "preview": "import { ActionTypeEnum, PluginRecord } from \"../../../../../src/types/Manager\";\nimport { t } from \"../../../../config/l"
  },
  {
    "path": "electron/mapi/manager/type.ts",
    "chars": 462,
    "preview": "import { BrowserView, BrowserWindow } from \"electron\";\nimport { ActiveWindow, PluginRecord } from \"../../../src/types/Ma"
  },
  {
    "path": "electron/mapi/manager/window/index.ts",
    "chars": 35558,
    "preview": "import * as remoteMain from \"@electron/remote/main\";\nimport {\n    BrowserView,\n    BrowserWindow,\n    screen,\n    shell,"
  },
  {
    "path": "electron/mapi/manager/window/remoteWeb.ts",
    "chars": 10723,
    "preview": "import { PluginRecord } from \"../../../../src/types/Manager\";\nimport { ManagerPlugin } from \"../plugin\";\nimport path fro"
  },
  {
    "path": "electron/mapi/misc/index.ts",
    "chars": 8751,
    "preview": "import archiver from \"archiver\";\nimport axios from \"axios\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimpo"
  },
  {
    "path": "electron/mapi/misc/main.ts",
    "chars": 441,
    "preview": "import { ipcMain } from \"electron\";\n\nimport index from \"./index\";\n\nipcMain.handle(\n    \"misc:getZipFileContent\",\n    asy"
  },
  {
    "path": "electron/mapi/misc/render.ts",
    "chars": 64,
    "preview": "import index from \"./index\";\n\nexport default {\n    ...index,\n};\n"
  },
  {
    "path": "electron/mapi/protocol/main.ts",
    "chars": 2550,
    "preview": "import { Log } from \"../log/main\";\n\nexport const ProtocolMain = {\n    isReady: false,\n    ready() {\n        this.isReady"
  },
  {
    "path": "electron/mapi/render.ts",
    "chars": 1400,
    "preview": "import { exposeContext } from \"./util\";\nimport { AppEnv } from \"./env\";\n\nimport config from \"./config/render\";\nimport lo"
  },
  {
    "path": "electron/mapi/statistics/render.ts",
    "chars": 1453,
    "preview": "import { AppConfig } from \"../../../src/config\";\nimport {\n    memoryInfo,\n    platformArch,\n    platformName,\n    platfo"
  },
  {
    "path": "electron/mapi/storage/main.ts",
    "chars": 2880,
    "preview": "import { AppEnv, waitAppEnvReady } from \"../env\";\nimport fs from \"node:fs\";\nimport { ipcMain } from \"electron\";\nimport n"
  },
  {
    "path": "electron/mapi/storage/render.ts",
    "chars": 994,
    "preview": "import { ipcRenderer } from \"electron\";\n\nconst all = async (group: string) => {\n    return ipcRenderer.invoke(\"storage:a"
  },
  {
    "path": "electron/mapi/ui/index.ts",
    "chars": 19,
    "preview": "export default {};\n"
  },
  {
    "path": "electron/mapi/ui/render.ts",
    "chars": 3920,
    "preview": "const init = () => {\n    // initLoaders()\n};\n\nconst initLoaders = () => {\n    function domReady(\n        condition: Docu"
  },
  {
    "path": "electron/mapi/updater/index.ts",
    "chars": 912,
    "preview": "import { AppConfig } from \"../../../src/config\";\nimport {\n    platformArch,\n    platformName,\n    platformUUID,\n    plat"
  },
  {
    "path": "electron/mapi/updater/main.ts",
    "chars": 453,
    "preview": "import updaterIndex from \"./index\";\nimport { ipcMain } from \"electron\";\nimport ConfigMain from \"../config/main\";\n\nipcMai"
  },
  {
    "path": "electron/mapi/updater/render.ts",
    "chars": 430,
    "preview": "import updaterIndex from \"./index\";\nimport { ipcRenderer } from \"electron\";\n\nconst getCheckAtLaunch = async (): Promise<"
  },
  {
    "path": "electron/mapi/user/main.ts",
    "chars": 7917,
    "preview": "import { ipcMain, shell } from \"electron\";\nimport { AppConfig } from \"../../../src/config\";\nimport { ResultType } from \""
  },
  {
    "path": "electron/mapi/user/render.ts",
    "chars": 946,
    "preview": "import { ipcRenderer } from \"electron\";\n\nconst open = async (option: any) => {\n    return ipcRenderer.invoke(\"user:open\""
  },
  {
    "path": "electron/mapi/util.ts",
    "chars": 313,
    "preview": "import { contextBridge } from \"electron\";\n\nexport function exposeContext(key, value) {\n    if (process.contextIsolated) "
  },
  {
    "path": "electron/page/about.ts",
    "chars": 1269,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { t } from \"../config/lang\";\nimport { WindowConfig } from \"../config/wi"
  },
  {
    "path": "electron/page/feedback.ts",
    "chars": 1296,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { t } from \"../config/lang\";\nimport { WindowConfig } from \"../config/wi"
  },
  {
    "path": "electron/page/guide.ts",
    "chars": 2525,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { preloadDefault, rendererLoadPath } from \"../lib/env-main\";\nimport { P"
  },
  {
    "path": "electron/page/index.ts",
    "chars": 2892,
    "preview": "import { Events } from \"../mapi/event/main\";\nimport { AppEnv, AppRuntime } from \"../mapi/env\";\nimport { PageUser } from "
  },
  {
    "path": "electron/page/log.ts",
    "chars": 1762,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { t } from \"../config/lang\";\nimport { WindowConfig } from \"../config/wi"
  },
  {
    "path": "electron/page/monitor.ts",
    "chars": 2490,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { t } from \"../config/lang\";\nimport { preloadDefault } from \"../lib/env"
  },
  {
    "path": "electron/page/payment.ts",
    "chars": 3844,
    "preview": "import { BrowserWindow, ipcMain } from \"electron\";\nimport { preloadDefault, rendererLoadPath } from \"../lib/env-main\";\ni"
  },
  {
    "path": "electron/page/setup.ts",
    "chars": 2559,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { preloadDefault, rendererLoadPath } from \"../lib/env-main\";\nimport { P"
  },
  {
    "path": "electron/page/user.ts",
    "chars": 1137,
    "preview": "import { BrowserWindow } from \"electron\";\nimport { t } from \"../config/lang\";\nimport { preloadDefault } from \"../lib/env"
  },
  {
    "path": "electron/preload/focusany.ts",
    "chars": 27766,
    "preview": "import electronRemote from \"@electron/remote\";\nimport { ipcRenderer, shell } from \"electron\";\nimport fs from \"fs\";\nimpor"
  },
  {
    "path": "electron/preload/index.ts",
    "chars": 7800,
    "preview": "import { ipcRenderer, webFrame } from \"electron\";\nimport { MAPI } from \"../mapi/render\";\nimport { FocusAny } from \"./foc"
  },
  {
    "path": "electron/preload/plugin.ts",
    "chars": 5457,
    "preview": "import { FocusAny } from \"./focusany\";\nimport { ipcRenderer, webFrame } from \"electron\";\n\nwebFrame.setZoomLevel(1);\nwebF"
  },
  {
    "path": "electron/resources/build/entitlements.mac.plist",
    "chars": 414,
    "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": "electron-builder.json5",
    "chars": 5482,
    "preview": "// @see https://www.electron.build/configuration/configuration\n{\n    \"$schema\": \"https://raw.githubusercontent.com/elect"
  },
  {
    "path": "entitlements.mac.plist",
    "chars": 888,
    "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": "index.html",
    "chars": 1498,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "package.json",
    "chars": 5126,
    "preview": "{\n    \"name\": \"focusany\",\n    \"version\": \"1.1.0\",\n    \"main\": \"dist-electron/main/index.js\",\n    \"description\": \"FocusAn"
  },
  {
    "path": "page/about.html",
    "chars": 341,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/detachWindow.html",
    "chars": 348,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/fastPanel.html",
    "chars": 345,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/feedback.html",
    "chars": 344,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/guide.html",
    "chars": 341,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/log.html",
    "chars": 339,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/monitor.html",
    "chars": 343,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/payment.html",
    "chars": 343,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/setup.html",
    "chars": 341,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/store.html",
    "chars": 341,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/system.html",
    "chars": 342,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/user.html",
    "chars": 340,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "page/workflow.html",
    "chars": 344,
    "preview": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\"/>\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/log"
  },
  {
    "path": "postcss.config.js",
    "chars": 93,
    "preview": "export default {\n    plugins: {\n        tailwindcss: {},\n        autoprefixer: {},\n    },\n};\n"
  },
  {
    "path": "public/iconfont/iconfont.css",
    "chars": 1312,
    "preview": "@font-face {\n  font-family: \"iconfont\"; /* Project id 4733566 */\n  src: url('iconfont.woff2?t=1736411931319') format('wo"
  },
  {
    "path": "public/iconfont/iconfont.js",
    "chars": 28708,
    "preview": "window._iconfont_svg_string_4733566='<svg><symbol id=\"icon-backend\" viewBox=\"0 0 1024 1024\"><path d=\"M187.7504 802.1504h"
  },
  {
    "path": "public/iconfont/iconfont.json",
    "chars": 3141,
    "preview": "{\n  \"id\": \"4733566\",\n  \"name\": \"FocusAny\",\n  \"font_family\": \"iconfont\",\n  \"css_prefix_text\": \"icon-\",\n  \"description\": \""
  },
  {
    "path": "public/static/pluginEmpty.html",
    "chars": 437,
    "preview": "<!doctype html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\"\n          content=\"width=de"
  },
  {
    "path": "scripts/build_optimize.cjs",
    "chars": 2394,
    "preview": "const common = require(\"./common.cjs\");\n\nconsole.log(\"BuildOptimize\", {\n    name: common.platformName(),\n    arch: commo"
  },
  {
    "path": "scripts/common.cjs",
    "chars": 3238,
    "preview": "const fs = require(\"node:fs\");\nconst {resolve, join} = require(\"node:path\");\nconst crypto = require(\"node:crypto\");\n\ncon"
  },
  {
    "path": "scripts/icon_convert.sh",
    "chars": 3106,
    "preview": "#!/bin/bash\n\n# prepare\n# brew install --cask inkscape\n\necho \"Convert icon\"\n\nDIR=\"$( cd \"$( dirname \"${BASH_SOURCE[0]}\" )"
  },
  {
    "path": "scripts/init.sh",
    "chars": 1926,
    "preview": "#!/usr/bin/env bash\n\nset -euo pipefail\n\nREPO_URL=\"https://github.com/modstart-lib/share-binary\"\nREPO_DIR=\"share-binary\"\n"
  },
  {
    "path": "scripts/notarize.cjs",
    "chars": 1851,
    "preview": "const {notarize} = require(\"@electron/notarize\");\nconst common = require('./common.cjs')\n\nexports.default = async functi"
  },
  {
    "path": "sdk/.babelrc",
    "chars": 390,
    "preview": "{\n  \"presets\": [\n    [\n      \"@babel/preset-env\",\n      {\n        \"targets\": {\n          \"browsers\": [\n            \"> 1%"
  },
  {
    "path": "sdk/.github/workflows/tag-release.yml",
    "chars": 748,
    "preview": "name: Publish to npm\n\non:\n    push:\n        tags:\n            - \"v*.*.*\" # 仅当推送匹配 vX.X.X 的标签时触发\n\njobs:\n    publish:\n    "
  },
  {
    "path": "sdk/.gitignore",
    "chars": 12,
    "preview": "*.tgz\n*.js\n\n"
  },
  {
    "path": "sdk/.npmignore",
    "chars": 22,
    "preview": "example/\n*.tgz\ntests/\n"
  },
  {
    "path": "sdk/.nvmrc",
    "chars": 3,
    "preview": "20\n"
  },
  {
    "path": "sdk/README.md",
    "chars": 1519,
    "preview": "# FocusAny SDK\n\nTypeScript definitions and utilities for FocusAny.\n\n## Installation\n\n```bash\nnpm install focusany-sdk\n``"
  },
  {
    "path": "sdk/bin/command.ts",
    "chars": 3552,
    "preview": "#!/usr/bin/env node\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport * as process from \"process\";\n\ninterf"
  },
  {
    "path": "sdk/config.schema.json",
    "chars": 21932,
    "preview": "{\n    \"$schema\": \"http://json-schema.org/draft-07/schema#\",\n    \"$id\": \"https://focusany.com/sdk/config.schema.json\",\n  "
  },
  {
    "path": "sdk/electron-browser-window.d.ts",
    "chars": 7586,
    "preview": "declare module BrowserWindow {\n    interface WebPreferences {\n        devTools?: boolean;\n        preload?: string;\n    "
  },
  {
    "path": "sdk/electron.d.ts",
    "chars": 2927,
    "preview": "declare module \"electron\" {\n    type ClipboardType = \"selection\" | \"clipboard\";\n    module clipboard {\n        function "
  },
  {
    "path": "sdk/focusany-shim.d.ts",
    "chars": 110,
    "preview": "export interface FocusAnyShimType {\n    init(): void;\n}\n\nexport declare const FocusAnyShim: FocusAnyShimType;\n"
  },
  {
    "path": "sdk/focusany-shim.ts",
    "chars": 40175,
    "preview": "/// <reference path=\"focusany.d.ts\" />\n\nconst FocusAnyShim = {\n    init() {\n        if (window[\"focusany\"]) {\n          "
  },
  {
    "path": "sdk/focusany.d.ts",
    "chars": 24687,
    "preview": "/// <reference path=\"electron-browser-window.d.ts\"/>\n/// <reference path=\"electron.d.ts\"/>\n\ndeclare interface Window {\n "
  },
  {
    "path": "sdk/index.d.ts",
    "chars": 117,
    "preview": "/// <reference path=\"focusany.d.ts\" />\n\n// 默认导出 focusany API\ndeclare const focusany: FocusAnyApi;\nexport = focusany;\n"
  }
]

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

About this extraction

This page contains the full source code of the modstart-lib/focusany GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 368 files (1.5 MB), approximately 365.7k tokens, and a symbol index with 843 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!