Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions .github/workflows/release-desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@ name: Release Desktop
# (previously `bump-cask: needs: build` waited for the whole matrix and was
# skipped if any leg failed, so macOS users were stranded on the old version).
#
# Unsigned by design (CSC_IDENTITY_AUTO_DISCOVERY=false).
# macOS: Apple Silicon only, signed with the Developer ID certificate and notarized
# (docs/electron-desktop/MAC-SIGNING.md). Without the MAC_CSC_* / APPLE_* secrets
# the mac leg FAILS — the app self-updates via Squirrel.Mac, which refuses unsigned
# updates. Windows/Linux stay unsigned (CSC_IDENTITY_AUTO_DISCOVERY=false).
on:
push:
tags: ['v*.*.*']
Expand All @@ -32,8 +35,31 @@ jobs:
- name: Build & publish desktop app (macOS)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
run: npx electron-builder --mac --arm64 --x64 --publish always
# MAC_-prefixed secrets: electron-builder reads CSC_LINK on Windows too, so the
# name has to say which platform this certificate belongs to.
CSC_LINK: ${{ secrets.MAC_CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.MAC_CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: |
set -euo pipefail
# No unsigned fallback. The app updates itself through Squirrel.Mac, which
# installs only an update signed by the same Developer ID — an unsigned
# release would be refused by every installed copy, and its dmg blocked by
# Gatekeeper for every new one.
if [ -z "${CSC_LINK:-}" ]; then
echo "::error::MAC_CSC_LINK is not set — refusing to publish an unsigned macOS build. See docs/electron-desktop/MAC-SIGNING.md."
exit 1
fi
if [ -z "${APPLE_ID:-}" ] || [ -z "${APPLE_APP_SPECIFIC_PASSWORD:-}" ] || [ -z "${APPLE_TEAM_ID:-}" ]; then
# electron-builder would sign, log "skipped macOS notarization" and publish.
# Gatekeeper refuses a signed-but-unnotarized download exactly like an
# unsigned one, so that build is a failure that looks like a success.
echo "::error::MAC_CSC_LINK is set but APPLE_ID / APPLE_APP_SPECIFIC_PASSWORD / APPLE_TEAM_ID are not — refusing to publish a signed but un-notarized dmg."
exit 1
fi
npx electron-builder --mac --arm64 --publish always

build-others:
strategy:
Expand Down Expand Up @@ -74,14 +100,19 @@ jobs:
VERSION="${GITHUB_REF_NAME#v}"
BASE="https://github.com/${GITHUB_REPOSITORY}/releases/download/${GITHUB_REF_NAME}"
curl -fL "$BASE/claude-code-studio-${VERSION}-arm64.dmg" -o arm64.dmg
curl -fL "$BASE/claude-code-studio-${VERSION}-x64.dmg" -o x64.dmg
ARM_SHA="$(shasum -a 256 arm64.dmg | awk '{print $1}')"
X64_SHA="$(shasum -a 256 x64.dmg | awk '{print $1}')"
git clone "https://x-access-token:${TAP_TOKEN}@github.com/${GITHUB_REPOSITORY_OWNER}/homebrew-claude-code-studio.git" tap
CASK="tap/Casks/claude-code-studio.rb"
sed -i -E "s/^ version \".*\"/ version \"${VERSION}\"/" "$CASK"
sed -i -E "s/(arm:[[:space:]]*\")[a-f0-9]{64}/\1${ARM_SHA}/" "$CASK"
sed -i -E "s/(intel:[[:space:]]*\")[a-f0-9]{64}/\1${X64_SHA}/" "$CASK"
sed -i -E "s/^ sha256 \"[a-f0-9]{64}\"/ sha256 \"${ARM_SHA}\"/" "$CASK"
# sed exits 0 when its pattern matches nothing. A cask in any other shape
# (the old per-arch `sha256 arm:/intel:` stanza) would be pushed with the
# NEW version and the OLD checksum — every install then fails brew's sha
# check. Refuse instead of publishing that.
grep -q "^ version \"${VERSION}\"$" "$CASK" && grep -q "^ sha256 \"${ARM_SHA}\"$" "$CASK" || {
echo "::error::$CASK was not updated to ${VERSION} / ${ARM_SHA} — it must carry a single-arch \`sha256 \"…\"\` line."
exit 1
}
cd tap
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ data/uploads/

# Environment
.env
# Local macOS signing/notarization settings (docs/electron-desktop/MAC-SIGNING.md)
electron-builder.env

# Workspace (user files, Claude working dir)
workspace/
Expand Down
35 changes: 33 additions & 2 deletions CLAUDE.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ Prefer a native window over a browser tab? Claude Code Studio also ships as a **

**Install** (from the [latest release](https://github.com/Lexus2016/claude-code-studio/releases/latest)):

- **macOS** — `brew install --cask Lexus2016/claude-code-studio/claude-code-studio`, or download the `.dmg`
- **macOS** (Apple Silicon) — download the `.dmg` (signed and notarized), open it and drag the app to Applications
- **Windows** — download the `…-Setup-….exe` installer
- **Linux** — download the `.AppImage` (portable) or `.deb`

**Prerequisite:** same as the web version — the [Claude Code CLI](https://docs.anthropic.com/en/claude-code) installed and logged in. The app detects it on launch and shows an install hint if it's missing.

**Built-in updates:** the app tells you when a new version ships and updates in one click — Windows/Linux update in place, macOS updates via Homebrew. Auto-update is opt-in (default: notify + one click).
**Built-in updates:** the app tells you when a new version ships and updates itself in one click on every OS. On macOS it has to run from Applications — started straight from the `.dmg` or from Downloads, it cannot replace itself and says so.

**Coming from the CLI/web version?** The desktop app keeps its own data folder, so you can pull your existing history and settings across in one step — no manual file copying:

Expand Down
4 changes: 2 additions & 2 deletions README_RU.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ Claude Code Studio решает эти проблемы:

**Установка** (из [последнего релиза](https://github.com/Lexus2016/claude-code-studio/releases/latest)):

- **macOS** — `brew install --cask Lexus2016/claude-code-studio/claude-code-studio` или скачайте `.dmg`
- **macOS** (Apple Silicon) — скачайте `.dmg` (подписан и нотаризован), откройте и перетащите приложение в «Программы»
- **Windows** — скачайте установщик `…-Setup-….exe`
- **Linux** — скачайте `.AppImage` (портативный) или `.deb`

**Требование:** то же, что и для веб-версии — установленный и залогиненный [Claude Code CLI](https://docs.anthropic.com/en/claude-code). Приложение проверяет его при запуске и показывает подсказку по установке, если его нет.

**Встроенные обновления:** приложение сообщает о новой версии и обновляется в один клик — Windows/Linux обновляются на месте, macOS — через Homebrew. Автообновление включается по желанию (по умолчанию: уведомление + один клик).
**Встроенные обновления:** приложение сообщает о новой версии и само обновляется в один клик на любой ОС. На macOS оно должно лежать в «Программах»: запущенное прямо из `.dmg` или из «Загрузок», оно не может заменить само себя и так и скажет.

**Переходите с консольной/веб-версии?** Десктоп-приложение хранит данные в собственной папке, поэтому перенести имеющуюся историю и настройки можно за один шаг — без ручного копирования файлов:

Expand Down
4 changes: 2 additions & 2 deletions README_UA.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ Claude Code Studio вирішує це:

**Встановлення** (з [останнього релізу](https://github.com/Lexus2016/claude-code-studio/releases/latest)):

- **macOS** — `brew install --cask Lexus2016/claude-code-studio/claude-code-studio` або завантажте `.dmg`
- **macOS** (Apple Silicon) — завантажте `.dmg` (підписаний і нотаризований), відкрийте й перетягніть застосунок у «Програми»
- **Windows** — завантажте інсталятор `…-Setup-….exe`
- **Linux** — завантажте `.AppImage` (портативний) або `.deb`

**Передумова:** та сама, що й для веб-версії — встановлений і залогінений [Claude Code CLI](https://docs.anthropic.com/en/claude-code). Застосунок перевіряє його при старті й показує підказку зі встановлення, якщо його немає.

**Вбудовані оновлення:** застосунок повідомляє про нову версію й оновлюється в один клік — Windows/Linux оновлюються на місці, macOS — через Homebrew. Авто-оновлення вмикається за бажанням (типово: сповіщення + один клік).
**Вбудовані оновлення:** застосунок повідомляє про нову версію й сам оновлюється в один клік на будь-якій ОС. На macOS він має бути в «Програмах»: запущений просто з `.dmg` чи із «Завантажень», він не може замінити сам себе й так і скаже.

**Переходите з консольної/веб-версії?** Десктоп-застосунок має власну теку даних, тож перенести наявну історію й налаштування можна за один крок — без ручного копіювання файлів:

Expand Down
22 changes: 22 additions & 0 deletions build/entitlements.mac.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- The three keys electron-builder's own template carries — the set it signs with
when no entitlements file exists. V8 needs JIT under the hardened runtime. -->
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
<!-- The reason this file exists. server.js drives Terminal.app through osascript
(`tell application "Terminal" to do script …`). Under the hardened runtime the
Automation check is made against THIS app, the responsible process, not against
osascript — without the key the event is refused (-1743) with no prompt at all.
Used for both the app and its helpers (entitlementsInherit): the server runs in
a utilityProcess, i.e. inside a Helper bundle. -->
<key>com.apple.security.automation.apple-events</key>
<true/>
</dict>
</plist>
19 changes: 13 additions & 6 deletions docs/electron-desktop/DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ window); a separate Electron fork of the app (duplicates ~17k LOC).
bridge (`checkUpdate / startUpdate / onUpdateLog / getVersion`) consumed by the SPA's update banner.
- `electron/update-macos.js` — **non-security-critical** helper: locate `brew`, spawn a detached
`brew upgrade --cask` + relaunch (§6). No crypto, no bundle-swapping of our own.
- `electron-builder.yml` — targets: macOS dmg + zip (x64 + arm64), Windows nsis, Linux
- `electron-builder.yml` — targets: macOS dmg + zip (arm64 only since 2026-09 — see `MAC-SIGNING.md`), Windows nsis, Linux
AppImage + deb; `asarUnpack` for spawned helpers; `publish: github`.
- `homebrew-tap/Casks/claude-code-studio.rb` — Cask (§7), with `xattr -cr` postflight.
- `package.json` additions — `devDependencies`: `electron`, `electron-builder`,
Expand Down Expand Up @@ -116,6 +116,10 @@ This is additive UI, not a change to existing web behavior.

## 6. Update Experience — in-app, GUI-driven (unsigned, tiered, zero custom crypto)

> **Superseded for macOS (2026-09).** The app is now Developer ID signed and notarized, and
> macOS updates through `electron-updater` (Squirrel.Mac) like Windows/Linux. The brew-driven
> flow below is history. See [`MAC-SIGNING.md`](MAC-SIGNING.md).

Updating is a **first-class GUI feature**: the desktop user always sees their version and whether
a newer one exists, and updates without leaving the app. No Apple signing, no custom crypto.

Expand Down Expand Up @@ -171,10 +175,10 @@ bundle-swapping. Homebrew's `xattr -cr` postflight handles Gatekeeper on macOS.
| 6 | Disabled on desktop | telegram bot, tunnel-manager, remote SSH — off by default |
| 7 | `claude` CLI | **Require + friendly auto-prompt**: detect at startup; if missing, show a friendly window with install instructions/link. No bundling. |
| 8 | **Framework** | **Electron** (not Tauri — §2; our backend is a multi-process Node app, not a single binary). |
| 9 | **Code signing** | **None.** Unsigned on all 3 OSes. Door open to Apple cert later. |
| 10 | **Auto-update** | **Tiered, unsigned, no custom crypto: Win/Linux = `electron-updater`; macOS = app-triggered `brew upgrade --cask` (detached) + relaunch** (§6). Not brew-managed → "install via Homebrew" guidance (no direct-dmg update path). |
| 11 | macOS distribution channels | **Homebrew Cask — the only supported macOS channel.** dmg/zip is built solely as the cask's source artifact, not a promoted direct install. Cask mirrors `homebrew-tap/Casks/localguard.rb`: `xattr -cr` postflight, **`auto_updates false`** (brew *is* the macOS updater). |
| 12 | Update UX | **In-app GUI: version banner + [Update] button + live report; [Copy] + [Open Terminal] command fallback; opt-in auto-update (default OFF).** Driven by preload `window.electronAPI`; absent (and invisible) in web mode. |
| 9 | **Code signing** | **macOS: Developer ID + notarization, arm64 only** (2026-09, [`MAC-SIGNING.md`](MAC-SIGNING.md)). Windows/Linux: unsigned. |
| 10 | **Auto-update** | **`electron-updater` on every OS** (macOS = Squirrel.Mac, signed updates only). Replaced the app-triggered `brew upgrade --cask` in 2026-09 — [`MAC-SIGNING.md`](MAC-SIGNING.md). |
| 11 | macOS distribution channels | **The signed, notarized `.dmg` from GitHub Releases.** The Homebrew cask is kept only as a transition path for installs that still update through brew, then retired (§9). |
| 12 | Update UX | **In-app GUI: version banner + [Update] button + live download progress; "move the app to Applications" when macOS cannot replace the bundle in place.** Driven by preload `window.electronAPI`; absent (and invisible) in web mode. |

---

Expand All @@ -196,6 +200,10 @@ bundle-swapping. Homebrew's `xattr -cr` postflight handles Gatekeeper on macOS.

## 9. macOS Distribution via Homebrew Tap (Cask) — the only supported macOS channel

> **Transitional since 2026-09.** Kept only so installs from before the signed release can reach
> it through their built-in `brew upgrade`; retired about a month later. See
> [`MAC-SIGNING.md`](MAC-SIGNING.md).

Tap repo `homebrew-claude-code-studio`, file `Casks/claude-code-studio.rb`:
`brew tap <owner>/claude-code-studio && brew install --cask claude-code-studio`. Thin wrapper
over the release dmg/zip (URL + `sha256`). Copy the proven pattern from
Expand Down Expand Up @@ -234,7 +242,6 @@ After all phases, the web version behaves exactly as today.
- Switching the desktop build to Tauri (§2 — wrong fit for a multi-process Node backend).
- A custom in-app macOS self-updater with own-key signing (replaced by brew-triggered upgrade).
- A supported direct-`.dmg` install/update channel on macOS (the dmg is a cask source artifact only).
- Apple code signing / notarization (revisit only if first-launch friction proves unacceptable).
- Rewriting any backend logic; splitting `public/index.html` (stays single-file).
- Bundling the `claude` CLI.
- Desktop variants of telegram/tunnel/SSH server features.
Expand Down
Loading
Loading