diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml
new file mode 100644
index 00000000..ce9f22e0
--- /dev/null
+++ b/.github/workflows/desktop-build.yml
@@ -0,0 +1,97 @@
+name: desktop-build
+
+on:
+ workflow_dispatch:
+ pull_request:
+ paths:
+ - 'src-tauri/**'
+ - 'tools/desktop/**'
+ - 'web-ui/**'
+ - 'cli.js'
+ - 'cli/**'
+ - 'lib/**'
+ - 'plugins/**'
+ - 'package.json'
+ - 'package-lock.json'
+ - '.github/workflows/desktop-build.yml'
+
+permissions:
+ contents: read
+
+jobs:
+ tauri:
+ name: ${{ matrix.name }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: macOS
+ os: macos-latest
+ - name: Windows
+ os: windows-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+ with:
+ persist-credentials: false
+
+ - name: Setup Node
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: '22'
+ cache: npm
+
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
+
+ - name: Install dependencies
+ run: npm ci
+
+ - name: Verify npm package payload
+ run: npm pack --dry-run --json
+
+ - name: Stage desktop runtime resources
+ run: npm run desktop:stage
+
+ - name: Build desktop app
+ run: npm run desktop:build
+
+ - name: Verify Windows app UAC manifest
+ if: matrix.name == 'Windows'
+ shell: pwsh
+ run: |
+ $exe = Join-Path $PWD 'src-tauri/target/release/codexmate-desktop.exe'
+ if (!(Test-Path $exe)) {
+ throw "Built app exe not found: $exe"
+ }
+
+ $mt = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter mt.exe |
+ Sort-Object FullName -Descending |
+ Select-Object -First 1
+ if (!$mt) {
+ throw 'Windows manifest tool mt.exe not found'
+ }
+
+ $manifest = Join-Path $env:RUNNER_TEMP 'codexmate-desktop.manifest.xml'
+ & $mt.FullName -nologo "-inputresource:$exe;#1" "-out:$manifest"
+ if ($LASTEXITCODE -ne 0) {
+ throw "mt.exe failed to extract manifest from $exe"
+ }
+
+ $text = Get-Content $manifest -Raw
+ Write-Host $text
+ if ($text -notmatch 'requestedExecutionLevel\s+level="requireAdministrator"\s+uiAccess="false"') {
+ throw 'Windows app manifest does not require administrator privileges'
+ }
+ Copy-Item $manifest (Join-Path (Split-Path $exe) 'codexmate-desktop.manifest.xml') -Force
+
+ - name: Upload desktop bundles
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ with:
+ name: codexmate-desktop-${{ matrix.name }}
+ path: |
+ src-tauri/target/release/bundle/**
+ src-tauri/target/release/codexmate-desktop.exe
+ src-tauri/target/release/codexmate-desktop.manifest.xml
+ if-no-files-found: error
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 829483ce..0ccf3ba7 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,4 +1,4 @@
-name: release
+name: release
run-name: "${{ github.event.repository.name }} ${{ inputs.tag || 'auto' }}"
on:
workflow_dispatch:
@@ -11,17 +11,28 @@ permissions:
contents: write
jobs:
- release:
+ resolve:
runs-on: ubuntu-latest
+ outputs:
+ release_tag: ${{ steps.resolve.outputs.release_tag }}
+ release_version: ${{ steps.resolve.outputs.release_version }}
+ release_mode: ${{ steps.resolve.outputs.release_mode }}
+ latest_tag: ${{ steps.resolve.outputs.latest_tag }}
+ package_version: ${{ steps.resolve.outputs.package_version }}
+ base_version: ${{ steps.resolve.outputs.base_version }}
+ base_source: ${{ steps.resolve.outputs.base_source }}
+ tag_exists: ${{ steps.resolve.outputs.tag_exists }}
steps:
- name: Checkout
- uses: actions/checkout@v4
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
with:
fetch-depth: 0
fetch-tags: true
+ persist-credentials: false
- name: Fetch tags
run: git fetch --tags --force
- - uses: actions/setup-node@v4
+ - name: Setup Node
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: '18'
cache: 'npm'
@@ -113,18 +124,6 @@ jobs:
baseSource = tagExists ? 'package_tag' : 'package_version';
}
- const envLines = [
- `RELEASE_TAG=${resolvedTag}`,
- `RELEASE_VERSION=${expectedVersion}`,
- `RELEASE_MODE=${mode}`,
- `LATEST_TAG=${latestTag}`,
- `PACKAGE_VERSION=${pkgVersion}`,
- `BASE_VERSION=${baseVersion}`,
- `BASE_SOURCE=${baseSource}`,
- `TAG_EXISTS=${tagExists ? 'true' : 'false'}`
- ].join('\n') + '\n';
- fs.appendFileSync(process.env.GITHUB_ENV, envLines);
-
const outputLines = [
`release_tag=${resolvedTag}`,
`release_version=${expectedVersion}`,
@@ -152,28 +151,98 @@ jobs:
fs.appendFileSync(process.env.GITHUB_STEP_SUMMARY, summaryLines + '\n');
console.log(`::notice title=Resolved Tag::${resolvedTag}`);
NODE
+
+ desktop:
+ needs: resolve
+ name: desktop-${{ matrix.name }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - name: macOS
+ os: macos-latest
+ - name: Windows
+ os: windows-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+ persist-credentials: false
+ - name: Fetch tags
+ run: git fetch --tags --force
- name: Checkout target tag
- if: ${{ steps.resolve.outputs.tag_exists == 'true' }}
+ if: ${{ needs.resolve.outputs.tag_exists == 'true' }}
env:
- RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
+ RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
git rev-parse "refs/tags/${RELEASE_TAG}" >/dev/null 2>&1
git checkout "${RELEASE_TAG}"
- - name: Verify tag matches package.json version
- if: ${{ steps.resolve.outputs.tag_exists == 'true' }}
+ - name: Setup Node
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: '22'
+ cache: npm
+ - name: Verify package.json matches release tag
+ env:
+ RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
+ run: |
+ node -e "const pkg=require('./package.json'); const tag=process.env.RELEASE_TAG; const expected='v'+pkg.version; if(tag!==expected){ console.error('package.json '+expected+' does not match resolved release tag '+tag); process.exit(1);} console.log('Package matches '+expected);"
+ - name: Setup Rust
+ uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8
+ - name: Install dependencies
+ run: npm ci
+ - name: Verify npm package payload
+ run: npm pack --dry-run --json
+ - name: Stage desktop runtime resources
+ run: npm run desktop:stage
+ - name: Build desktop app
+ run: npm run desktop:build
+ - name: Upload desktop release assets
+ uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
+ with:
+ name: codexmate-desktop-${{ matrix.name }}
+ path: |
+ src-tauri/target/release/bundle/dmg/*.dmg
+ src-tauri/target/release/bundle/msi/*.msi
+ src-tauri/target/release/bundle/nsis/*.exe
+ if-no-files-found: error
+
+ release:
+ needs:
+ - resolve
+ - desktop
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5
+ with:
+ fetch-depth: 0
+ fetch-tags: true
+ persist-credentials: false
+ - name: Fetch tags
+ run: git fetch --tags --force
+ - name: Checkout target tag
+ if: ${{ needs.resolve.outputs.tag_exists == 'true' }}
env:
- RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
+ RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
- node -e "const pkg=require('./package.json'); const tag=process.env.RELEASE_TAG; const expected='v'+pkg.version; if(tag!==expected){ console.error('Tag '+tag+' does not match package.json version '+expected); process.exit(1);} console.log('Tag matches '+expected);"
+ git rev-parse "refs/tags/${RELEASE_TAG}" >/dev/null 2>&1
+ git checkout "${RELEASE_TAG}"
+ - name: Setup Node
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
+ with:
+ node-version: '18'
- name: Verify package.json matches release tag
- if: ${{ steps.resolve.outputs.tag_exists != 'true' }}
env:
- RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
+ RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
- node -e "const pkg=require('./package.json'); const tag=process.env.RELEASE_TAG; const expected='v'+pkg.version; if(tag!==expected){ console.error('Current commit package.json '+expected+' does not match resolved release tag '+tag); process.exit(1);} console.log('Current package matches '+expected);"
+ node -e "const pkg=require('./package.json'); const tag=process.env.RELEASE_TAG; const expected='v'+pkg.version; if(tag!==expected){ console.error('package.json '+expected+' does not match resolved release tag '+tag); process.exit(1);} console.log('Package matches '+expected);"
- name: Compute release name
env:
- RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
+ RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
run: |
node -e "const p=require('./package.json'); const tag=process.env.RELEASE_TAG; const name=p.name.includes('/')? p.name.split('/')[1]: p.name; const value=name+' '+tag; console.log('RELEASE_NAME='+value);" >> "$GITHUB_ENV"
- name: Pack npm artifact
@@ -191,11 +260,17 @@ jobs:
cli.js cli/ lib/ plugins/ web-ui.html web-ui/ \
node_modules/ package.json LICENSE README.md README.zh.md
echo "STANDALONE_TGZ=$name" >> "$GITHUB_ENV"
+ - name: Download desktop release assets
+ uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
+ with:
+ pattern: codexmate-desktop-*
+ path: desktop-release-assets
+ merge-multiple: true
- name: Fetch contributors from GitHub API
env:
GH_TOKEN: ${{ github.token }}
- RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
- LATEST_TAG: ${{ steps.resolve.outputs.latest_tag }}
+ RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
+ LATEST_TAG: ${{ needs.resolve.outputs.latest_tag }}
CONTRIBUTORS_FILE: release-contributors.txt
run: |
if [ -z "${LATEST_TAG}" ]; then
@@ -213,22 +288,13 @@ jobs:
trap 'rm -f "${tmp_logins}"' EXIT
# Fetch PR authors in range using base...head comparison
- gh pr list \
- --repo "${GITHUB_REPOSITORY}" \
- --limit 500 \
- --json author \
- --jq '.[].author.login' 2>/dev/null | sort -u > "${tmp_logins}" || true
+ gh pr list --repo "${GITHUB_REPOSITORY}" --limit 500 --json author --jq '.[].author.login' 2>/dev/null | sort -u > "${tmp_logins}" || true
# Fetch merged PRs in range using commits
tmp_merged=$(mktemp)
- git log "${LATEST_TAG}...${RELEASE_TAG}" --pretty=format:%s \
- | grep -oE '#[0-9]+' \
- | sed 's/^#//' \
- | sort -u \
- | while read -r pr_number; do
+ git log "${LATEST_TAG}...${RELEASE_TAG}" --pretty=format:%s | grep -oE '#[0-9]+' | sed 's/^#//' | sort -u | while read -r pr_number; do
gh pr view "${pr_number}" --repo "${GITHUB_REPOSITORY}" --json author --jq '.author.login' 2>/dev/null || true
- done \
- | sort -u > "${tmp_merged}" || true
+ done | sort -u > "${tmp_merged}" || true
if [ -s "${tmp_merged}" ]; then
cat "${tmp_merged}" > "${CONTRIBUTORS_FILE}"
@@ -240,8 +306,8 @@ jobs:
fi
- name: Generate release notes from actual commit range
env:
- RELEASE_TAG: ${{ steps.resolve.outputs.release_tag }}
- TAG_EXISTS: ${{ steps.resolve.outputs.tag_exists }}
+ RELEASE_TAG: ${{ needs.resolve.outputs.release_tag }}
+ TAG_EXISTS: ${{ needs.resolve.outputs.tag_exists }}
RELEASE_CHANGELOG_FILE: release-changelog.md
CONTRIBUTORS_FILE: release-contributors.txt
run: |
@@ -256,9 +322,9 @@ jobs:
node tools/release/changelog.js
test -s "${RELEASE_CHANGELOG_FILE}"
- name: Create GitHub Release
- uses: softprops/action-gh-release@v2
+ uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65
with:
- tag_name: ${{ steps.resolve.outputs.release_tag }}
+ tag_name: ${{ needs.resolve.outputs.release_tag }}
target_commitish: ${{ github.sha }}
name: ${{ env.RELEASE_NAME }}
prerelease: false
@@ -267,4 +333,7 @@ jobs:
files: |
${{ env.PACKAGE_TGZ }}
${{ env.STANDALONE_TGZ }}
+ desktop-release-assets/**/*.dmg
+ desktop-release-assets/**/*.msi
+ desktop-release-assets/**/*.exe
generate_release_notes: false
diff --git a/.gitignore b/.gitignore
index 3581375f..ddf9a500 100644
--- a/.gitignore
+++ b/.gitignore
@@ -56,4 +56,3 @@ codex-switcher.exe
log.txt
tmp/
.gitnexus/
-
diff --git a/doc/desktop.md b/doc/desktop.md
new file mode 100644
index 00000000..3c8bd119
--- /dev/null
+++ b/doc/desktop.md
@@ -0,0 +1,92 @@
+# Codex Mate Desktop (Tauri)
+
+Codex Mate 的桌面版使用 Tauri 作为 Windows / macOS 外壳,复用现有 Node CLI 与 Web UI 服务。
+
+## 架构
+
+- Tauri 负责桌面窗口、系统打包和平台安装包。
+- 现有 `cli.js run --host 127.0.0.1 --no-browser` 继续提供本地 Web UI 与 `/api`。
+- 桌面窗口加载 `http://127.0.0.1:3737`,避免重写现有 Web UI API。
+- Rust / Tauri 源码只参与桌面构建阶段,不进入主 npm CLI 包。
+- `npm run desktop:stage` 会先生成稳定运行时目录 `dist/desktop/codexmate/`,再由 Tauri 把这个目录作为单一 resource 打进 app。
+- 打包产物内置构建机当前 Node.js runtime,release 启动后端时优先使用 bundled `node-runtime/node(.exe)`,不依赖用户系统 PATH 里的 `node`。
+
+## Staging 布局
+
+`tools/desktop/prepare-tauri-resources.js` 参考 Codex 的“先 stage、再打包、再校验”模型,生成的目录大致是:
+
+```text
+dist/desktop/codexmate/
+├── codexmate-desktop.json
+├── cli.js
+├── cli/
+├── lib/
+├── plugins/
+├── web-ui/
+├── web-ui.html
+├── package.json
+├── package-lock.json
+├── node-runtime/ # bundled Node.js runtime used by release desktop startup
+└── node_modules/ # package-lock 中非 dev 的运行时依赖
+```
+
+脚本会验证入口文件、Web UI、manifest、`node_modules`、bundled Node runtime 和直接运行时依赖是否存在。这样可以提前暴露资源缺失,而不是等 `tauri build` 通过后才在用户机器上启动失败。
+
+## 命令
+
+```bash
+npm run desktop:stage
+npm run desktop:prepare # desktop:stage 的兼容别名
+npm run desktop:dev
+npm run desktop:build
+```
+
+## 本地要求
+
+桌面构建需要:
+
+- Node.js 18+
+- Rust / Cargo
+- Tauri 对应平台依赖
+
+release 桌面包会内置 Node.js runtime 来启动打包进 resources 的 Codex Mate 后端;用户机器不需要预装 Node.js。调试或排障时仍可用 `CODEXMATE_NODE=/path/to/node` 显式覆盖 runtime。
+
+## 启动诊断日志
+
+Windows release 包仍使用 GUI subsystem,普通双击不会弹出黑色控制台。需要快速定位启动闪退时,可以从 PowerShell / CMD 显式启用控制台日志:
+
+```powershell
+codexmate-desktop.exe --debug-console
+```
+
+也可以通过环境变量启用:
+
+```powershell
+$env:CODEXMATE_DESKTOP_LOG = "1"
+codexmate-desktop.exe
+```
+
+启用后,桌面壳会尝试附着父控制台,打印 Rust/Tauri 启动日志,并让内置 Node backend 的 stdout/stderr 继承到当前终端。无论是否启用控制台,桌面壳都会写入本地文件日志;未启用控制台时,backend stdout/stderr 会写入 `startup.log`:
+
+```text
+%LOCALAPPDATA%\CodexMate\logs\desktop.log
+%LOCALAPPDATA%\CodexMate\logs\startup.log
+```
+
+如需指定 `desktop.log` 位置:
+
+```powershell
+$env:CODEXMATE_DESKTOP_LOG_FILE = "$env:TEMP\codexmate-desktop.log"
+codexmate-desktop.exe --debug-console
+```
+
+## CI
+
+`.github/workflows/desktop-build.yml` 会在 GitHub Actions 上:
+
+- `npm ci` 安装依赖
+- `npm pack --dry-run --json` 验证主 npm CLI 包 payload
+- `npm run desktop:stage` 验证桌面运行时 staging
+- 在 macOS / Windows 上执行 `npm run desktop:build`
+
+构建产物会以 `codexmate-desktop-macOS` / `codexmate-desktop-Windows` artifact 上传。
diff --git a/package-lock.json b/package-lock.json
index 00705df7..49f4fe4c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -18,12 +18,13 @@
"codexmate": "cli.js"
},
"devDependencies": {
+ "@tauri-apps/cli": "^2.11.2",
"@vue/compiler-dom": "^3.5.30",
"opencc-js": "^1.3.1",
"vitepress": "^1.6.4"
},
"engines": {
- "node": ">=14"
+ "node": ">=16.14.0"
}
},
"node_modules/@algolia/abtesting": {
@@ -1243,6 +1244,223 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@tauri-apps/cli": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.3.tgz",
+ "integrity": "sha512-EElQe8z8uD7Pi5++tJ/UfEwWuK08rd3oCDYdeIbJAb6pZRrxlqmoF5gh5H5YvzmUPhS4IRCaLSsQhvWkrfK+GQ==",
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "bin": {
+ "tauri": "tauri.js"
+ },
+ "engines": {
+ "node": ">= 10"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/tauri"
+ },
+ "optionalDependencies": {
+ "@tauri-apps/cli-darwin-arm64": "2.11.3",
+ "@tauri-apps/cli-darwin-x64": "2.11.3",
+ "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.3",
+ "@tauri-apps/cli-linux-arm64-gnu": "2.11.3",
+ "@tauri-apps/cli-linux-arm64-musl": "2.11.3",
+ "@tauri-apps/cli-linux-riscv64-gnu": "2.11.3",
+ "@tauri-apps/cli-linux-x64-gnu": "2.11.3",
+ "@tauri-apps/cli-linux-x64-musl": "2.11.3",
+ "@tauri-apps/cli-win32-arm64-msvc": "2.11.3",
+ "@tauri-apps/cli-win32-ia32-msvc": "2.11.3",
+ "@tauri-apps/cli-win32-x64-msvc": "2.11.3"
+ }
+ },
+ "node_modules/@tauri-apps/cli-darwin-arm64": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.3.tgz",
+ "integrity": "sha512-BxpaM8bsCoXs3wd4WKYhas/G1gs7+r7B+e4WnyRk2GEoVOouJB1hoL6E6YLXZDXbYci6VFdrNnobQwd2uVL4ew==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-darwin-x64": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.3.tgz",
+ "integrity": "sha512-DbZYuPB1ZEzcAHYeyCvo3ltzM27+aXwPloCrtexPnmgPgulYJm3TOq6aC4S+wPhSXteddg8zImtNkvx/gQzmwg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.3.tgz",
+ "integrity": "sha512-741NduqBmz1XkdU8yz3OI/kBZtqHbvxo9F9ytIeWYU69/Ba9dcZEbqOU++Dp0G/XU8vAI0TfTywEl+p+BbLvaA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm64-gnu": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.3.tgz",
+ "integrity": "sha512-RWAXT8pTqIczXcoic+LXlo6uEbAXGB0cgh6Pg7Y9xVnEbzryQ1JHtRGj9SxzrKSemBIDBH6Qc24kK2G69i8ofA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-arm64-musl": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.3.tgz",
+ "integrity": "sha512-qomqYS+yAkd0gXMRmhguWXc7RfVN+XKKXaEwbf5QmKURwydLFOTldd6F8/WoZDSsBMrV8dpNxz0YneGLmobiSA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-riscv64-gnu": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.3.tgz",
+ "integrity": "sha512-jOCXbDqeDj5XcclsOBAaXjtTgwZCVg8zEZ+dbPUCoADOgljFgL0rOkYTc96vUYgOrYEfuHYihWMxIDGaD6GwJw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-x64-gnu": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.3.tgz",
+ "integrity": "sha512-+u3HO/F3gHwL48t9gWN/urqZvpaEJzBFmTaq5eSIhvy8TOvnhb+LgJr3Q3BG+5JxuBrCUjqtOEz6gMttdJFSBA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-linux-x64-musl": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.3.tgz",
+ "integrity": "sha512-spr5Jpr6KF/vehkLwJ0YmdGv8QwpWU+uw7J8bgijO0sox6ZCYsSNMbcsQjTqPi4xl+p0woIYpWXgChgHYpAc8g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-arm64-msvc": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.3.tgz",
+ "integrity": "sha512-abkoRQih5xBa3vz2spWaex0kP/MzVzVPQHom2f8jnCq46R/luOD6Uy85EMU9/bfzf6ZzdorWJsgO+OMX90Fx2w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-ia32-msvc": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.3.tgz",
+ "integrity": "sha512-Vy6AvzFm1G40hg3r+OYDB3jkuu7R4wnMzbQBKuun9v6Cgg8IierpLL7toMzrZKs/8NlG8Sg4x1iLFR52oknyHg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@tauri-apps/cli-win32-x64-msvc": {
+ "version": "2.11.3",
+ "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.3.tgz",
+ "integrity": "sha512-GlciF75GdbseajOyib2aCHwE3BXIqZ1liGKWLFRvCdN5wm8h8hFssEVKQ/6E+2jsMLg9v7LCTb983YFnn0QSww==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "Apache-2.0 OR MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@types/estree": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
diff --git a/package.json b/package.json
index 939bde63..e50f2f0d 100644
--- a/package.json
+++ b/package.json
@@ -43,7 +43,11 @@
"test:e2e": "node tests/e2e/run.js",
"setup:git": "git remote set-url origin https://github.com/SakuraByteCore/codexmate.git && gh auth setup-git",
"reset:dev": "node tools/dev/reset-and-dev.js",
- "pretest": "node tools/ci/ensure-test-deps.js"
+ "pretest": "node tools/ci/ensure-test-deps.js",
+ "desktop:prepare": "node tools/desktop/prepare-tauri-resources.js",
+ "desktop:stage": "node tools/desktop/prepare-tauri-resources.js",
+ "desktop:dev": "tauri dev",
+ "desktop:build": "tauri build"
},
"dependencies": {
"@iarna/toml": "^2.2.5",
@@ -52,7 +56,7 @@
"zip-lib": "^1.2.1"
},
"engines": {
- "node": ">=14"
+ "node": ">=16.14.0"
},
"keywords": [
"codex",
@@ -72,6 +76,7 @@
"author": "ymkiux",
"license": "Apache-2.0",
"devDependencies": {
+ "@tauri-apps/cli": "^2.11.2",
"@vue/compiler-dom": "^3.5.30",
"opencc-js": "^1.3.1",
"vitepress": "^1.6.4"
diff --git a/site/.vitepress/public/images/logo-v.png b/site/.vitepress/public/images/logo-v.png
new file mode 100644
index 00000000..df7dac25
Binary files /dev/null and b/site/.vitepress/public/images/logo-v.png differ
diff --git a/site/.vitepress/public/images/logo.png b/site/.vitepress/public/images/logo.png
index f55f2a06..543df20d 100644
Binary files a/site/.vitepress/public/images/logo.png and b/site/.vitepress/public/images/logo.png differ
diff --git a/site/.vitepress/public/images/logo.svg b/site/.vitepress/public/images/logo.svg
deleted file mode 100644
index de9f8fce..00000000
--- a/site/.vitepress/public/images/logo.svg
+++ /dev/null
@@ -1,10 +0,0 @@
-
diff --git a/site/.vitepress/public/images/web-ui-screenshot.png b/site/.vitepress/public/images/web-ui-screenshot.png
deleted file mode 100644
index 008e87a9..00000000
Binary files a/site/.vitepress/public/images/web-ui-screenshot.png and /dev/null differ
diff --git a/src-tauri/.gitignore b/src-tauri/.gitignore
new file mode 100644
index 00000000..502406b4
--- /dev/null
+++ b/src-tauri/.gitignore
@@ -0,0 +1,4 @@
+# Generated by Cargo
+# will have compiled files and executables
+/target/
+/gen/schemas
diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml
new file mode 100644
index 00000000..a44f69a5
--- /dev/null
+++ b/src-tauri/Cargo.toml
@@ -0,0 +1,25 @@
+[package]
+name = "codexmate-desktop"
+version = "0.0.55"
+description = "Codex Mate desktop shell"
+authors = ["ymkiux"]
+license = "Apache-2.0"
+repository = "https://github.com/SakuraByteCore/codexmate"
+edition = "2021"
+rust-version = "1.77.2"
+
+# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+
+[lib]
+name = "app_lib"
+crate-type = ["staticlib", "cdylib", "rlib"]
+
+[build-dependencies]
+tauri-build = { version = "2.6.2" }
+
+[dependencies]
+serde_json = "1.0"
+serde = { version = "1.0", features = ["derive"] }
+log = "0.4"
+tauri = { version = "2.11.2" }
+tauri-plugin-log = "2"
diff --git a/src-tauri/app.manifest b/src-tauri/app.manifest
new file mode 100644
index 00000000..bb86d44b
--- /dev/null
+++ b/src-tauri/app.manifest
@@ -0,0 +1,23 @@
+
+
+ Codex Mate
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src-tauri/build.rs b/src-tauri/build.rs
new file mode 100644
index 00000000..86bd0b07
--- /dev/null
+++ b/src-tauri/build.rs
@@ -0,0 +1,6 @@
+fn main() {
+ let windows = tauri_build::WindowsAttributes::new()
+ .app_manifest(include_str!("app.manifest"));
+ let attrs = tauri_build::Attributes::new().windows_attributes(windows);
+ tauri_build::try_build(attrs).expect("failed to run tauri build script");
+}
diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json
new file mode 100644
index 00000000..c135d7f1
--- /dev/null
+++ b/src-tauri/capabilities/default.json
@@ -0,0 +1,11 @@
+{
+ "$schema": "../gen/schemas/desktop-schema.json",
+ "identifier": "default",
+ "description": "enables the default permissions",
+ "windows": [
+ "main"
+ ],
+ "permissions": [
+ "core:default"
+ ]
+}
diff --git a/src-tauri/icons/128x128.png b/src-tauri/icons/128x128.png
new file mode 100644
index 00000000..a0f1a31d
Binary files /dev/null and b/src-tauri/icons/128x128.png differ
diff --git a/src-tauri/icons/128x128@2x.png b/src-tauri/icons/128x128@2x.png
new file mode 100644
index 00000000..bc9e1ba2
Binary files /dev/null and b/src-tauri/icons/128x128@2x.png differ
diff --git a/src-tauri/icons/32x32.png b/src-tauri/icons/32x32.png
new file mode 100644
index 00000000..baeac996
Binary files /dev/null and b/src-tauri/icons/32x32.png differ
diff --git a/src-tauri/icons/64x64.png b/src-tauri/icons/64x64.png
new file mode 100644
index 00000000..21296e9f
Binary files /dev/null and b/src-tauri/icons/64x64.png differ
diff --git a/src-tauri/icons/Square107x107Logo.png b/src-tauri/icons/Square107x107Logo.png
new file mode 100644
index 00000000..9e19fb35
Binary files /dev/null and b/src-tauri/icons/Square107x107Logo.png differ
diff --git a/src-tauri/icons/Square142x142Logo.png b/src-tauri/icons/Square142x142Logo.png
new file mode 100644
index 00000000..47a00808
Binary files /dev/null and b/src-tauri/icons/Square142x142Logo.png differ
diff --git a/src-tauri/icons/Square150x150Logo.png b/src-tauri/icons/Square150x150Logo.png
new file mode 100644
index 00000000..23415a8b
Binary files /dev/null and b/src-tauri/icons/Square150x150Logo.png differ
diff --git a/src-tauri/icons/Square284x284Logo.png b/src-tauri/icons/Square284x284Logo.png
new file mode 100644
index 00000000..6df70fc6
Binary files /dev/null and b/src-tauri/icons/Square284x284Logo.png differ
diff --git a/src-tauri/icons/Square30x30Logo.png b/src-tauri/icons/Square30x30Logo.png
new file mode 100644
index 00000000..d4a04e0b
Binary files /dev/null and b/src-tauri/icons/Square30x30Logo.png differ
diff --git a/src-tauri/icons/Square310x310Logo.png b/src-tauri/icons/Square310x310Logo.png
new file mode 100644
index 00000000..63fa7ea0
Binary files /dev/null and b/src-tauri/icons/Square310x310Logo.png differ
diff --git a/src-tauri/icons/Square44x44Logo.png b/src-tauri/icons/Square44x44Logo.png
new file mode 100644
index 00000000..fcdf4944
Binary files /dev/null and b/src-tauri/icons/Square44x44Logo.png differ
diff --git a/src-tauri/icons/Square71x71Logo.png b/src-tauri/icons/Square71x71Logo.png
new file mode 100644
index 00000000..6e98c5e3
Binary files /dev/null and b/src-tauri/icons/Square71x71Logo.png differ
diff --git a/src-tauri/icons/Square89x89Logo.png b/src-tauri/icons/Square89x89Logo.png
new file mode 100644
index 00000000..07d87d7f
Binary files /dev/null and b/src-tauri/icons/Square89x89Logo.png differ
diff --git a/src-tauri/icons/StoreLogo.png b/src-tauri/icons/StoreLogo.png
new file mode 100644
index 00000000..0b856f97
Binary files /dev/null and b/src-tauri/icons/StoreLogo.png differ
diff --git a/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 00000000..2ffbf24b
--- /dev/null
+++ b/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 00000000..8fa307e7
Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..6545a7b2
Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 00000000..58f97e0b
Binary files /dev/null and b/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 00000000..837dd105
Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..8c2a10f4
Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 00000000..d87036b7
Binary files /dev/null and b/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 00000000..05596269
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..059f76ba
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..86b09ecf
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 00000000..8e9c8d97
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..53640c02
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..d6c5e504
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 00000000..ba8cd84a
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 00000000..44112298
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 00000000..9edb8cb6
Binary files /dev/null and b/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/src-tauri/icons/android/values/ic_launcher_background.xml b/src-tauri/icons/android/values/ic_launcher_background.xml
new file mode 100644
index 00000000..ea9c223a
--- /dev/null
+++ b/src-tauri/icons/android/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #fff
+
\ No newline at end of file
diff --git a/src-tauri/icons/icon.icns b/src-tauri/icons/icon.icns
new file mode 100644
index 00000000..f042c6f0
Binary files /dev/null and b/src-tauri/icons/icon.icns differ
diff --git a/src-tauri/icons/icon.ico b/src-tauri/icons/icon.ico
new file mode 100644
index 00000000..eee92d90
Binary files /dev/null and b/src-tauri/icons/icon.ico differ
diff --git a/src-tauri/icons/icon.png b/src-tauri/icons/icon.png
new file mode 100644
index 00000000..535442de
Binary files /dev/null and b/src-tauri/icons/icon.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@1x.png b/src-tauri/icons/ios/AppIcon-20x20@1x.png
new file mode 100644
index 00000000..69f9c204
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png
new file mode 100644
index 00000000..08e3eeea
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@2x.png b/src-tauri/icons/ios/AppIcon-20x20@2x.png
new file mode 100644
index 00000000..08e3eeea
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-20x20@3x.png b/src-tauri/icons/ios/AppIcon-20x20@3x.png
new file mode 100644
index 00000000..018de6cd
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-20x20@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@1x.png b/src-tauri/icons/ios/AppIcon-29x29@1x.png
new file mode 100644
index 00000000..ae72f38b
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png
new file mode 100644
index 00000000..10d8a7e4
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@2x.png b/src-tauri/icons/ios/AppIcon-29x29@2x.png
new file mode 100644
index 00000000..10d8a7e4
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-29x29@3x.png b/src-tauri/icons/ios/AppIcon-29x29@3x.png
new file mode 100644
index 00000000..2ffc3401
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-29x29@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@1x.png b/src-tauri/icons/ios/AppIcon-40x40@1x.png
new file mode 100644
index 00000000..08e3eeea
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png
new file mode 100644
index 00000000..600e0b55
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@2x.png b/src-tauri/icons/ios/AppIcon-40x40@2x.png
new file mode 100644
index 00000000..600e0b55
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-40x40@3x.png b/src-tauri/icons/ios/AppIcon-40x40@3x.png
new file mode 100644
index 00000000..02cf990c
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-40x40@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-512@2x.png b/src-tauri/icons/ios/AppIcon-512@2x.png
new file mode 100644
index 00000000..7ecfb4a0
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-512@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-60x60@2x.png b/src-tauri/icons/ios/AppIcon-60x60@2x.png
new file mode 100644
index 00000000..02cf990c
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-60x60@3x.png b/src-tauri/icons/ios/AppIcon-60x60@3x.png
new file mode 100644
index 00000000..1f3fd0cc
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-60x60@3x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-76x76@1x.png b/src-tauri/icons/ios/AppIcon-76x76@1x.png
new file mode 100644
index 00000000..805e71d6
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@1x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-76x76@2x.png b/src-tauri/icons/ios/AppIcon-76x76@2x.png
new file mode 100644
index 00000000..03369bb3
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-76x76@2x.png differ
diff --git a/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png
new file mode 100644
index 00000000..8c002427
Binary files /dev/null and b/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ
diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs
new file mode 100644
index 00000000..3454a809
--- /dev/null
+++ b/src-tauri/src/lib.rs
@@ -0,0 +1,722 @@
+use std::{
+ fs::{self, OpenOptions},
+ io::{Read, Write},
+ net::{SocketAddr, TcpStream},
+ path::PathBuf,
+ process::{Child, Command, Stdio},
+ sync::{
+ atomic::{AtomicBool, Ordering},
+ Mutex,
+ },
+ time::{Duration, Instant, SystemTime, UNIX_EPOCH},
+};
+
+#[cfg(windows)]
+use std::os::windows::process::CommandExt;
+
+use tauri::{Manager, WindowEvent};
+
+struct BackendState(Mutex