Sfoglia il codice sorgente

desktop: add electron version (#15663)

Brendan Allan 1 mese fa
parent
commit
5cf235fa6c
100 ha cambiato i file con 705 aggiunte e 41 eliminazioni
  1. 126 1
      .github/workflows/publish.yml
  2. 308 9
      bun.lock
  3. 2 1
      package.json
  4. 7 4
      packages/app/src/app.tsx
  5. 2 0
      packages/app/src/components/titlebar.tsx
  6. 2 0
      packages/app/src/pages/layout.tsx
  7. 19 18
      packages/app/src/utils/notification-click.test.ts
  8. 8 8
      packages/app/src/utils/notification-click.ts
  9. 28 0
      packages/desktop-electron/.gitignore
  10. 4 0
      packages/desktop-electron/AGENTS.md
  11. 32 0
      packages/desktop-electron/README.md
  12. 97 0
      packages/desktop-electron/electron-builder.config.ts
  13. 41 0
      packages/desktop-electron/electron.vite.config.ts
  14. 11 0
      packages/desktop-electron/icons/README.md
  15. BIN
      packages/desktop-electron/icons/beta/128x128.png
  16. BIN
      packages/desktop-electron/icons/beta/[email protected]
  17. BIN
      packages/desktop-electron/icons/beta/32x32.png
  18. BIN
      packages/desktop-electron/icons/beta/64x64.png
  19. BIN
      packages/desktop-electron/icons/beta/Square107x107Logo.png
  20. BIN
      packages/desktop-electron/icons/beta/Square142x142Logo.png
  21. BIN
      packages/desktop-electron/icons/beta/Square150x150Logo.png
  22. BIN
      packages/desktop-electron/icons/beta/Square284x284Logo.png
  23. BIN
      packages/desktop-electron/icons/beta/Square30x30Logo.png
  24. BIN
      packages/desktop-electron/icons/beta/Square310x310Logo.png
  25. BIN
      packages/desktop-electron/icons/beta/Square44x44Logo.png
  26. BIN
      packages/desktop-electron/icons/beta/Square71x71Logo.png
  27. BIN
      packages/desktop-electron/icons/beta/Square89x89Logo.png
  28. BIN
      packages/desktop-electron/icons/beta/StoreLogo.png
  29. 5 0
      packages/desktop-electron/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml
  30. BIN
      packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher.png
  31. BIN
      packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png
  32. BIN
      packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_round.png
  33. BIN
      packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher.png
  34. BIN
      packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png
  35. BIN
      packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_round.png
  36. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher.png
  37. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png
  38. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png
  39. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher.png
  40. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png
  41. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png
  42. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png
  43. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png
  44. BIN
      packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png
  45. 4 0
      packages/desktop-electron/icons/beta/android/values/ic_launcher_background.xml
  46. BIN
      packages/desktop-electron/icons/beta/icon.icns
  47. BIN
      packages/desktop-electron/icons/beta/icon.ico
  48. BIN
      packages/desktop-electron/icons/beta/icon.png
  49. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  50. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  51. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  52. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  53. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  54. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  55. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  56. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  57. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  58. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  59. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  60. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  61. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  62. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  63. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  64. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  65. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  66. BIN
      packages/desktop-electron/icons/beta/ios/[email protected]
  67. BIN
      packages/desktop-electron/icons/dev/128x128.png
  68. BIN
      packages/desktop-electron/icons/dev/[email protected]
  69. BIN
      packages/desktop-electron/icons/dev/32x32.png
  70. BIN
      packages/desktop-electron/icons/dev/64x64.png
  71. BIN
      packages/desktop-electron/icons/dev/Square107x107Logo.png
  72. BIN
      packages/desktop-electron/icons/dev/Square142x142Logo.png
  73. BIN
      packages/desktop-electron/icons/dev/Square150x150Logo.png
  74. BIN
      packages/desktop-electron/icons/dev/Square284x284Logo.png
  75. BIN
      packages/desktop-electron/icons/dev/Square30x30Logo.png
  76. BIN
      packages/desktop-electron/icons/dev/Square310x310Logo.png
  77. BIN
      packages/desktop-electron/icons/dev/Square44x44Logo.png
  78. BIN
      packages/desktop-electron/icons/dev/Square71x71Logo.png
  79. BIN
      packages/desktop-electron/icons/dev/Square89x89Logo.png
  80. BIN
      packages/desktop-electron/icons/dev/StoreLogo.png
  81. 5 0
      packages/desktop-electron/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml
  82. BIN
      packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher.png
  83. BIN
      packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png
  84. BIN
      packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_round.png
  85. BIN
      packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher.png
  86. BIN
      packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png
  87. BIN
      packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_round.png
  88. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher.png
  89. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png
  90. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png
  91. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher.png
  92. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png
  93. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png
  94. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png
  95. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png
  96. BIN
      packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png
  97. 4 0
      packages/desktop-electron/icons/dev/android/values/ic_launcher_background.xml
  98. BIN
      packages/desktop-electron/icons/dev/icon.icns
  99. BIN
      packages/desktop-electron/icons/dev/icon.ico
  100. BIN
      packages/desktop-electron/icons/dev/icon.png

+ 126 - 1
.github/workflows/publish.yml

@@ -99,7 +99,6 @@ jobs:
         with:
           name: opencode-cli
           path: packages/opencode/dist
-
     outputs:
       version: ${{ needs.version.outputs.version }}
 
@@ -240,11 +239,130 @@ jobs:
           APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
           APPLE_API_KEY_PATH: ${{ runner.temp }}/apple-api-key.p8
 
+  build-electron:
+    needs:
+      - build-cli
+      - version
+    continue-on-error: false
+    strategy:
+      fail-fast: false
+      matrix:
+        settings:
+          - host: macos-latest
+            target: x86_64-apple-darwin
+            platform_flag: --mac --x64
+          - host: macos-latest
+            target: aarch64-apple-darwin
+            platform_flag: --mac --arm64
+          - host: "blacksmith-4vcpu-windows-2025"
+            target: x86_64-pc-windows-msvc
+            platform_flag: --win
+          - host: "blacksmith-4vcpu-ubuntu-2404"
+            target: x86_64-unknown-linux-gnu
+            platform_flag: --linux
+          - host: "blacksmith-4vcpu-ubuntu-2404"
+            target: aarch64-unknown-linux-gnu
+            platform_flag: --linux
+    runs-on: ${{ matrix.settings.host }}
+    steps:
+      - uses: actions/checkout@v3
+
+      - uses: apple-actions/import-codesign-certs@v2
+        if: runner.os == 'macOS'
+        with:
+          keychain: build
+          p12-file-base64: ${{ secrets.APPLE_CERTIFICATE }}
+          p12-password: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+
+      - name: Setup Apple API Key
+        if: runner.os == 'macOS'
+        run: echo "${{ secrets.APPLE_API_KEY_PATH }}" > $RUNNER_TEMP/apple-api-key.p8
+
+      - uses: ./.github/actions/setup-bun
+
+      - uses: actions/setup-node@v4
+        with:
+          node-version: "24"
+
+      - name: Cache apt packages
+        if: contains(matrix.settings.host, 'ubuntu')
+        uses: actions/cache@v4
+        with:
+          path: ~/apt-cache
+          key: ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-${{ hashFiles('.github/workflows/publish.yml') }}
+          restore-keys: |
+            ${{ runner.os }}-${{ matrix.settings.target }}-apt-electron-
+
+      - name: Install dependencies (ubuntu only)
+        if: contains(matrix.settings.host, 'ubuntu')
+        run: |
+          mkdir -p ~/apt-cache && chmod -R a+rw ~/apt-cache
+          sudo apt-get update
+          sudo apt-get install -y --no-install-recommends -o dir::cache::archives="$HOME/apt-cache" rpm
+          sudo chmod -R a+rw ~/apt-cache
+
+      - name: Setup git committer
+        id: committer
+        uses: ./.github/actions/setup-git-committer
+        with:
+          opencode-app-id: ${{ vars.OPENCODE_APP_ID }}
+          opencode-app-secret: ${{ secrets.OPENCODE_APP_SECRET }}
+
+      - name: Prepare
+        run: bun ./scripts/prepare.ts
+        working-directory: packages/desktop-electron
+        env:
+          OPENCODE_VERSION: ${{ needs.version.outputs.version }}
+          OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+          RUST_TARGET: ${{ matrix.settings.target }}
+          GH_TOKEN: ${{ github.token }}
+          GITHUB_RUN_ID: ${{ github.run_id }}
+
+      - name: Build
+        run: bun run build
+        working-directory: packages/desktop-electron
+        env:
+          OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+
+      - name: Package and publish
+        if: needs.version.outputs.release
+        run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish always --config electron-builder.config.ts
+        working-directory: packages/desktop-electron
+        timeout-minutes: 60
+        env:
+          OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+          GH_TOKEN: ${{ steps.committer.outputs.token }}
+          CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }}
+          CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
+          APPLE_API_KEY: ${{ runner.temp }}/apple-api-key.p8
+          APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY }}
+          APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
+
+      - name: Package (no publish)
+        if: ${{ !needs.version.outputs.release }}
+        run: npx electron-builder ${{ matrix.settings.platform_flag }} --publish never --config electron-builder.config.ts
+        working-directory: packages/desktop-electron
+        timeout-minutes: 60
+        env:
+          OPENCODE_CHANNEL: ${{ (github.ref_name == 'beta' && 'beta') || 'prod' }}
+
+      - uses: actions/upload-artifact@v4
+        with:
+          name: opencode-electron-${{ matrix.settings.target }}
+          path: packages/desktop-electron/dist/*
+
+      - uses: actions/upload-artifact@v4
+        if: needs.version.outputs.release
+        with:
+          name: latest-yml-${{ matrix.settings.target }}
+          path: packages/desktop-electron/dist/latest*.yml
+
   publish:
     needs:
       - version
       - build-cli
       - build-tauri
+      - build-electron
     runs-on: blacksmith-4vcpu-ubuntu-2404
     steps:
       - uses: actions/checkout@v3
@@ -281,6 +399,12 @@ jobs:
           name: opencode-cli
           path: packages/opencode/dist
 
+      - uses: actions/download-artifact@v4
+        if: needs.version.outputs.release
+        with:
+          pattern: latest-yml-*
+          path: /tmp/latest-yml
+
       - name: Cache apt packages (AUR)
         uses: actions/cache@v4
         with:
@@ -308,3 +432,4 @@ jobs:
           GITHUB_TOKEN: ${{ steps.committer.outputs.token }}
           GH_REPO: ${{ needs.version.outputs.repo }}
           NPM_CONFIG_PROVENANCE: false
+          LATEST_YML_DIR: /tmp/latest-yml

File diff suppressed because it is too large
+ 308 - 9
bun.lock


+ 2 - 1
package.json

@@ -99,7 +99,8 @@
     "protobufjs",
     "tree-sitter",
     "tree-sitter-bash",
-    "web-tree-sitter"
+    "web-tree-sitter",
+    "electron"
   ],
   "overrides": {
     "@types/bun": "catalog:",

+ 7 - 4
packages/app/src/app.tsx

@@ -7,8 +7,8 @@ import { MarkedProvider } from "@opencode-ai/ui/context/marked"
 import { Font } from "@opencode-ai/ui/font"
 import { ThemeProvider } from "@opencode-ai/ui/theme"
 import { MetaProvider } from "@solidjs/meta"
-import { Navigate, Route, Router } from "@solidjs/router"
-import { ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js"
+import { BaseRouterProps, Navigate, Route, Router } from "@solidjs/router"
+import { Component, ErrorBoundary, type JSX, lazy, type ParentProps, Show, Suspense } from "solid-js"
 import { CommandProvider } from "@/context/command"
 import { CommentsProvider } from "@/context/comments"
 import { FileProvider } from "@/context/file"
@@ -28,6 +28,7 @@ import { TerminalProvider } from "@/context/terminal"
 import DirectoryLayout from "@/pages/directory-layout"
 import Layout from "@/pages/layout"
 import { ErrorPage } from "./pages/error"
+import { Dynamic } from "solid-js/web"
 
 const Home = lazy(() => import("@/pages/home"))
 const Session = lazy(() => import("@/pages/session"))
@@ -144,13 +145,15 @@ export function AppInterface(props: {
   children?: JSX.Element
   defaultServer: ServerConnection.Key
   servers?: Array<ServerConnection.Any>
+  router?: Component<BaseRouterProps>
 }) {
   return (
     <ServerProvider defaultServer={props.defaultServer} servers={props.servers}>
       <ServerKey>
         <GlobalSDKProvider>
           <GlobalSyncProvider>
-            <Router
+            <Dynamic
+              component={props.router ?? Router}
               root={(routerProps) => <RouterRoot appChildren={props.children}>{routerProps.children}</RouterRoot>}
             >
               <Route path="/" component={HomeRoute} />
@@ -158,7 +161,7 @@ export function AppInterface(props: {
                 <Route path="/" component={SessionIndexRoute} />
                 <Route path="/session/:id?" component={SessionRoute} />
               </Route>
-            </Router>
+            </Dynamic>
           </GlobalSyncProvider>
         </GlobalSDKProvider>
       </ServerKey>

+ 2 - 0
packages/app/src/components/titlebar.tsx

@@ -157,6 +157,7 @@ export function Titlebar() {
     <header
       class="h-10 shrink-0 bg-background-base relative grid grid-cols-[auto_minmax(0,1fr)_auto] items-center"
       style={{ "min-height": minHeight() }}
+      data-tauri-drag-region
       onMouseDown={drag}
       onDblClick={maximize}
     >
@@ -276,6 +277,7 @@ export function Titlebar() {
           "flex items-center min-w-0 justify-end": true,
           "pr-2": !windows(),
         }}
+        data-tauri-drag-region
         onMouseDown={drag}
       >
         <div id="opencode-titlebar-right" class="flex items-center gap-1 shrink-0 justify-end" />

+ 2 - 0
packages/app/src/pages/layout.tsx

@@ -42,6 +42,7 @@ import { Binary } from "@opencode-ai/util/binary"
 import { retry } from "@opencode-ai/util/retry"
 import { playSound, soundSrc } from "@/utils/sound"
 import { createAim } from "@/utils/aim"
+import { setNavigate } from "@/utils/notification-click"
 import { Worktree as WorktreeState } from "@/utils/worktree"
 
 import { useDialog } from "@opencode-ai/ui/context/dialog"
@@ -107,6 +108,7 @@ export default function Layout(props: ParentProps) {
   const notification = useNotification()
   const permission = usePermission()
   const navigate = useNavigate()
+  setNavigate(navigate)
   const providers = useProviders()
   const dialog = useDialog()
   const command = useCommand()

+ 19 - 18
packages/app/src/utils/notification-click.test.ts

@@ -1,26 +1,27 @@
-import { describe, expect, test } from "bun:test"
-import { handleNotificationClick } from "./notification-click"
+import { afterEach, describe, expect, test } from "bun:test"
+import { handleNotificationClick, setNavigate } from "./notification-click"
 
 describe("notification click", () => {
-  test("focuses and navigates when href exists", () => {
+  afterEach(() => {
+    setNavigate(undefined as any)
+  })
+
+  test("navigates via registered navigate function", () => {
     const calls: string[] = []
-    handleNotificationClick("/abc/session/123", {
-      focus: () => calls.push("focus"),
-      location: {
-        assign: (href) => calls.push(href),
-      },
-    })
-    expect(calls).toEqual(["focus", "/abc/session/123"])
+    setNavigate((href) => calls.push(href))
+    handleNotificationClick("/abc/session/123")
+    expect(calls).toEqual(["/abc/session/123"])
   })
 
-  test("only focuses when href is missing", () => {
+  test("does not navigate when href is missing", () => {
     const calls: string[] = []
-    handleNotificationClick(undefined, {
-      focus: () => calls.push("focus"),
-      location: {
-        assign: (href) => calls.push(href),
-      },
-    })
-    expect(calls).toEqual(["focus"])
+    setNavigate((href) => calls.push(href))
+    handleNotificationClick(undefined)
+    expect(calls).toEqual([])
+  })
+
+  test("falls back to location.assign without registered navigate", () => {
+    handleNotificationClick("/abc/session/123")
+    // falls back to window.location.assign — no error thrown
   })
 })

+ 8 - 8
packages/app/src/utils/notification-click.ts

@@ -1,12 +1,12 @@
-type WindowTarget = {
-  focus: () => void
-  location: {
-    assign: (href: string) => void
-  }
+let nav: ((href: string) => void) | undefined
+
+export const setNavigate = (fn: (href: string) => void) => {
+  nav = fn
 }
 
-export const handleNotificationClick = (href?: string, target: WindowTarget = window) => {
-  target.focus()
+export const handleNotificationClick = (href?: string) => {
+  window.focus()
   if (!href) return
-  target.location.assign(href)
+  if (nav) nav(href)
+  else window.location.assign(href)
 }

+ 28 - 0
packages/desktop-electron/.gitignore

@@ -0,0 +1,28 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
+out/
+
+resources/opencode-cli*
+resources/icons

+ 4 - 0
packages/desktop-electron/AGENTS.md

@@ -0,0 +1,4 @@
+# Desktop package notes
+
+- Renderer process should only call `window.api` from `src/preload`.
+- Main process should register IPC handlers in `src/main/ipc.ts`.

+ 32 - 0
packages/desktop-electron/README.md

@@ -0,0 +1,32 @@
+# OpenCode Desktop
+
+Native OpenCode desktop app, built with Tauri v2.
+
+## Development
+
+From the repo root:
+
+```bash
+bun install
+bun run --cwd packages/desktop tauri dev
+```
+
+This starts the Vite dev server on http://localhost:1420 and opens the native window.
+
+If you only want the web dev server (no native shell):
+
+```bash
+bun run --cwd packages/desktop dev
+```
+
+## Build
+
+To create a production `dist/` and build the native app bundle:
+
+```bash
+bun run --cwd packages/desktop tauri build
+```
+
+## Prerequisites
+
+Running the desktop app requires additional Tauri dependencies (Rust toolchain, platform-specific libraries). See the [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) for setup instructions.

+ 97 - 0
packages/desktop-electron/electron-builder.config.ts

@@ -0,0 +1,97 @@
+import type { Configuration } from "electron-builder"
+
+const channel = (() => {
+  const raw = process.env.OPENCODE_CHANNEL
+  if (raw === "dev" || raw === "beta" || raw === "prod") return raw
+  return "dev"
+})()
+
+const getBase = (): Configuration => ({
+  artifactName: "opencode-electron-${os}-${arch}.${ext}",
+  directories: {
+    output: "dist",
+    buildResources: "resources",
+  },
+  files: ["out/**/*", "resources/**/*"],
+  extraResources: [
+    {
+      from: "resources/",
+      to: "",
+      filter: ["opencode-cli*"],
+    },
+    {
+      from: "native/",
+      to: "native/",
+      filter: ["index.js", "index.d.ts", "build/Release/mac_window.node", "swift-build/**"],
+    },
+  ],
+  mac: {
+    category: "public.app-category.developer-tools",
+    icon: `resources/icons/icon.icns`,
+    hardenedRuntime: true,
+    gatekeeperAssess: false,
+    entitlements: "resources/entitlements.plist",
+    entitlementsInherit: "resources/entitlements.plist",
+    notarize: true,
+    target: ["dmg", "zip"],
+  },
+  dmg: {
+    sign: true,
+  },
+  protocols: {
+    name: "OpenCode",
+    schemes: ["opencode"],
+  },
+  win: {
+    icon: `resources/icons/icon.ico`,
+    target: ["nsis"],
+  },
+  nsis: {
+    oneClick: false,
+    allowToChangeInstallationDirectory: true,
+    installerIcon: `resources/icons/icon.ico`,
+    installerHeaderIcon: `resources/icons/icon.ico`,
+  },
+  linux: {
+    icon: `resources/icons`,
+    category: "Development",
+    target: ["AppImage", "deb", "rpm"],
+  },
+})
+
+function getConfig() {
+  const base = getBase()
+
+  switch (channel) {
+    case "dev": {
+      return {
+        ...base,
+        appId: "ai.opencode.desktop.dev",
+        productName: "OpenCode Dev",
+        rpm: { packageName: "opencode-dev" },
+      }
+    }
+    case "beta": {
+      return {
+        ...base,
+        appId: "ai.opencode.desktop.beta",
+        productName: "OpenCode Beta",
+        protocols: { name: "OpenCode Beta", schemes: ["opencode"] },
+        publish: { provider: "github", owner: "anomalyco", repo: "opencode-beta", channel: "latest" },
+        rpm: { packageName: "opencode-beta" },
+      }
+    }
+    case "prod": {
+      return {
+        ...base,
+        appId: "ai.opencode.desktop",
+        productName: "OpenCode",
+        protocols: { name: "OpenCode", schemes: ["opencode"] },
+        publish: { provider: "github", owner: "anomalyco", repo: "opencode", channel: "latest" },
+        rpm: { packageName: "opencode" },
+      }
+    }
+  }
+}
+
+export default getConfig()

+ 41 - 0
packages/desktop-electron/electron.vite.config.ts

@@ -0,0 +1,41 @@
+import { defineConfig } from "electron-vite"
+import appPlugin from "@opencode-ai/app/vite"
+
+const channel = (() => {
+  const raw = process.env.OPENCODE_CHANNEL
+  if (raw === "dev" || raw === "beta" || raw === "prod") return raw
+  return "dev"
+})()
+
+export default defineConfig({
+  main: {
+    define: {
+      "import.meta.env.OPENCODE_CHANNEL": JSON.stringify(channel),
+    },
+    build: {
+      rollupOptions: {
+        input: { index: "src/main/index.ts" },
+      },
+    },
+  },
+  preload: {
+    build: {
+      rollupOptions: {
+        input: { index: "src/preload/index.ts" },
+      },
+    },
+  },
+  renderer: {
+    plugins: [appPlugin],
+    publicDir: "../app/public",
+    root: "src/renderer",
+    build: {
+      rollupOptions: {
+        input: {
+          main: "src/renderer/index.html",
+          loading: "src/renderer/loading.html",
+        },
+      },
+    },
+  },
+})

+ 11 - 0
packages/desktop-electron/icons/README.md

@@ -0,0 +1,11 @@
+# Tauri Icons
+
+Here's the process I've been using to create icons:
+
+- Save source image as `app-icon.png` in `packages/desktop`
+- `cd` to `packages/desktop`
+- Run `bun tauri icon -o src-tauri/icons/{environment}`
+- Use [Image2Icon](https://img2icnsapp.com/)'s 'Big Sur Icon' preset to generate an `icon.icns` file and place it in the appropriate icons folder
+
+The Image2Icon step is necessary as the `icon.icns` generated by `app-icon.png` does not apply the shadow/padding expected by macOS,
+so app icons appear larger than expected.

BIN
packages/desktop-electron/icons/beta/128x128.png


BIN
packages/desktop-electron/icons/beta/[email protected]


BIN
packages/desktop-electron/icons/beta/32x32.png


BIN
packages/desktop-electron/icons/beta/64x64.png


BIN
packages/desktop-electron/icons/beta/Square107x107Logo.png


BIN
packages/desktop-electron/icons/beta/Square142x142Logo.png


BIN
packages/desktop-electron/icons/beta/Square150x150Logo.png


BIN
packages/desktop-electron/icons/beta/Square284x284Logo.png


BIN
packages/desktop-electron/icons/beta/Square30x30Logo.png


BIN
packages/desktop-electron/icons/beta/Square310x310Logo.png


BIN
packages/desktop-electron/icons/beta/Square44x44Logo.png


BIN
packages/desktop-electron/icons/beta/Square71x71Logo.png


BIN
packages/desktop-electron/icons/beta/Square89x89Logo.png


BIN
packages/desktop-electron/icons/beta/StoreLogo.png


+ 5 - 0
packages/desktop-electron/icons/beta/android/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+  <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+  <background android:drawable="@color/ic_launcher_background"/>
+</adaptive-icon>

BIN
packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-hdpi/ic_launcher_round.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-mdpi/ic_launcher_round.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xhdpi/ic_launcher_round.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xxhdpi/ic_launcher_round.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/beta/android/mipmap-xxxhdpi/ic_launcher_round.png


+ 4 - 0
packages/desktop-electron/icons/beta/android/values/ic_launcher_background.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="ic_launcher_background">#fff</color>
+</resources>

BIN
packages/desktop-electron/icons/beta/icon.icns


BIN
packages/desktop-electron/icons/beta/icon.ico


BIN
packages/desktop-electron/icons/beta/icon.png


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/beta/ios/[email protected]


BIN
packages/desktop-electron/icons/dev/128x128.png


BIN
packages/desktop-electron/icons/dev/[email protected]


BIN
packages/desktop-electron/icons/dev/32x32.png


BIN
packages/desktop-electron/icons/dev/64x64.png


BIN
packages/desktop-electron/icons/dev/Square107x107Logo.png


BIN
packages/desktop-electron/icons/dev/Square142x142Logo.png


BIN
packages/desktop-electron/icons/dev/Square150x150Logo.png


BIN
packages/desktop-electron/icons/dev/Square284x284Logo.png


BIN
packages/desktop-electron/icons/dev/Square30x30Logo.png


BIN
packages/desktop-electron/icons/dev/Square310x310Logo.png


BIN
packages/desktop-electron/icons/dev/Square44x44Logo.png


BIN
packages/desktop-electron/icons/dev/Square71x71Logo.png


BIN
packages/desktop-electron/icons/dev/Square89x89Logo.png


BIN
packages/desktop-electron/icons/dev/StoreLogo.png


+ 5 - 0
packages/desktop-electron/icons/dev/android/mipmap-anydpi-v26/ic_launcher.xml

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
+  <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
+  <background android:drawable="@color/ic_launcher_background"/>
+</adaptive-icon>

BIN
packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-hdpi/ic_launcher_round.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-mdpi/ic_launcher_round.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xhdpi/ic_launcher_round.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xxhdpi/ic_launcher_round.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_foreground.png


BIN
packages/desktop-electron/icons/dev/android/mipmap-xxxhdpi/ic_launcher_round.png


+ 4 - 0
packages/desktop-electron/icons/dev/android/values/ic_launcher_background.xml

@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+  <color name="ic_launcher_background">#fff</color>
+</resources>

BIN
packages/desktop-electron/icons/dev/icon.icns


BIN
packages/desktop-electron/icons/dev/icon.ico


BIN
packages/desktop-electron/icons/dev/icon.png


Some files were not shown because too many files changed in this diff