Skip to content
Open
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
25 changes: 25 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,28 @@ jobs:
# the Pages workflow on `main`, so a change that breaks its Vite build would
# otherwise stay invisible until it hit Pages. Build it on PRs too.
- run: bun --filter @llm-space/web build

windows:
runs-on: windows-2025
timeout-minutes: 30
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1

- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0

- run: bun install --frozen-lockfile

- run: mise run test

- run: mise run lint

- run: mise run typecheck

# pack:win includes the production renderer build. Verification inspects
# the compressed tar without extracting into the checkout, then the smoke
# test installs and launches it under an isolated temporary LOCALAPPDATA.
- run: mise run pack:win

- run: mise run verify:pack:win

- run: mise run smoke:win
73 changes: 68 additions & 5 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ name: Release
# which bumps apps/desktop/package.json, commits and tags).
# A `-canary` suffix in the tag selects the canary channel; otherwise stable.
#
# Jobs: meta (validate tag) → build (macOS arm64 + x64 matrix: sign, notarize,
# smoke-test) → publish. Artifacts land in two places:
# Jobs: meta (validate tag) → macOS + Windows builds (platform-native smoke
# tests) → publish. Artifacts land in two places:
# - the rolling `updates` release — the machine-readable update feed the
# in-app updater polls (`release.baseUrl` in electrobun.config.ts)
# - a versioned release per tag — DMGs for humans (static notes)
Expand Down Expand Up @@ -182,6 +182,9 @@ jobs:
# The edition's only build-time switch: electrobun.config.ts forks the
# app name, identifier and update feed off it. Empty = regular edition.
LLM_SPACE_DESKTOP_RENDERER: ${{ matrix.edition == 'performance' && 'cef' || '' }}
# Keep releases from forks self-contained instead of baking the
# canonical upstream feed into their binaries.
LLM_SPACE_UPDATE_BASE_URL: ${{ github.server_url }}/${{ github.repository }}/releases/download/${{ matrix.edition == 'performance' && 'updates-performance' || 'updates' }}
run: mise run "build:$CHANNEL"

- name: Smoke test the signed app
Expand Down Expand Up @@ -242,8 +245,43 @@ jobs:
if: always()
run: security delete-keychain build.keychain || true

# Windows is a separate host-native job: the macOS matrix is intentionally
# left Apple-specific (codesign, notarization, DMG and launch checks).
build-windows:
needs: meta
runs-on: windows-2025
timeout-minutes: 45
steps:
- uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
ref: ${{ needs.meta.outputs.tag }}

- uses: jdx/mise-action@e6a8b3978addb5a52f2b4cd9d91eafa7f0ab959d # v4.2.0

- run: bun install --frozen-lockfile

- name: Build unsigned Windows x64 regular edition
env:
CHANNEL: ${{ needs.meta.outputs.channel }}
LLM_SPACE_SKIP_SIGNING: "1"
LLM_SPACE_UPDATE_BASE_URL: ${{ github.server_url }}/${{ github.repository }}/releases/download/updates
run: mise run "build:$env:CHANNEL"

- run: mise run verify:pack:win

- run: mise run smoke:win

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
# Only Canary joins the regular publish set. Stable is built and smoke
# tested for forward compatibility but remains unpublished until the
# Authenticode/Stable follow-up is complete.
name: ${{ needs.meta.outputs.channel == 'canary' && 'artifacts-regular-win-x64' || 'artifacts-windows-stable-validation' }}
path: apps/desktop/artifacts/
if-no-files-found: error

publish:
needs: [meta, build]
needs: [meta, build, build-windows]
runs-on: ubuntu-latest
# Uploading two editions' worth of artifacts; the Performance tarballs are
# an order of magnitude bigger than the regular ones.
Expand Down Expand Up @@ -329,6 +367,13 @@ jobs:
rename_dmgs artifacts/regular LLMSpace
rename_dmgs artifacts/performance LLMSpace-performance

# Windows is Canary-only in this PR. Keep the Setup ZIP out of the
# rolling feed and give the versioned-release download a stable name.
shopt -s nullglob
for f in artifacts/regular/*-win-x64-*Setup*.zip; do
mv "$f" "artifacts/regular/LLMSpace-$VLOWER-windows-x64.zip"
done

- name: Publish versioned release
env:
TAG: ${{ needs.meta.outputs.tag }}
Expand All @@ -348,14 +393,27 @@ jobs:
INSTALL="$(cat <<'EOF'
## Install

Two editions. Install either, or both — they share the same `~/.llm-space` data, so switching keeps your threads and settings.
### macOS

Install either edition, or both — they share the same `~/.llm-space` data, so switching keeps your threads and settings.

- **LLM Space** — uses the system WebView. Small download (~27 MB), light on memory and battery.
- **LLM Space Performance** — embeds its own rendering engine (~130 MB). Rendering stays consistent across macOS versions, and usually performs better.

Download a DMG below. Existing installs update in place.
EOF
)"
if [ "$CHANNEL" = "canary" ]; then
WINDOWS="$(cat <<'EOF'
### Windows x64 Canary

Download `LLMSpace-*-windows-x64.zip`, extract it, and run the included `LLM Space-Setup-canary.exe`. This early Canary is **unsigned**, so Microsoft Defender SmartScreen may warn before launch.

Windows 10/11 x64 uses the system WebView2 runtime. There is no native ARM64 or Performance/CEF package yet, and Windows does not register the `llm-space://` URL scheme. To import a shared thread, copy its HTTPS viewer URL and choose **File → Import from Clipboard** in the app.
EOF
)"
INSTALL="$(printf '%s\n\n%s' "$INSTALL" "$WINDOWS")"
fi
if [ -n "$CHANGES" ]; then
NOTES="$(printf "## What's Changed\n\n%s\n\n%s\n\n**Full changelog:** https://github.com/%s/blob/%s/CHANGELOG.md\n" "$CHANGES" "$INSTALL" "$GITHUB_REPOSITORY" "$TAG")"
else
Expand Down Expand Up @@ -390,5 +448,10 @@ jobs:
# "LLMSpace-v4.0.1-macos-arm64.dmg" vs
# "LLMSpace-performance-v4.0.1-macos-arm64.dmg"). Only update.json is
# name-less, which is why the feeds above are kept apart.
gh release upload "$TAG" artifacts/regular/*.dmg artifacts/performance/*.dmg \
INSTALLERS=(artifacts/regular/*.dmg artifacts/performance/*.dmg)
if [ "$CHANNEL" = "canary" ]; then
shopt -s nullglob
INSTALLERS+=(artifacts/regular/*.zip)
fi
gh release upload "$TAG" "${INSTALLERS[@]}" \
--repo "$GITHUB_REPOSITORY" --clobber
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,21 @@ apps/

## Download

Grab a DMG from the [latest release](https://github.com/deer-flow/llm-space/releases/latest) — macOS, Apple Silicon and Intel. It comes in two editions:
### macOS

Grab a DMG from the [latest release](https://github.com/deer-flow/llm-space/releases/latest) — Apple Silicon and Intel are supported. It comes in two editions:

- **LLM Space** — uses the system WebView. Small download (~27 MB), light on memory and battery.
- **LLM Space Performance** — embeds its own rendering engine (~130 MB). Rendering stays consistent across macOS versions, and usually performs better.

Install either, or both. They share the same `~/.llm-space` data, so switching editions keeps your threads and settings, and both update themselves in place.

### Windows x64 Canary

Windows 10/11 x64 users can download `LLMSpace-*-windows-x64.zip` from a [Canary prerelease](https://github.com/deer-flow/llm-space/releases), extract it, and run the included `LLM Space-Setup-canary.exe`. This early Canary is unsigned, so Microsoft Defender SmartScreen may warn before launch. It uses the system WebView2 runtime; there is no Windows Performance/CEF or native ARM64 build yet.

Known Canary limitations: Windows does not register the `llm-space://` URL scheme, WebView2 can appear soft at high display scaling, and dragging a file from Explorer may temporarily move focus away from the app. To import a shared thread, copy its HTTPS viewer URL and choose **File → Import from Clipboard**.

## Install

Building from source? You need [Bun](https://bun.com) first. Bun is a fast, all-in-one runtime and package manager for JavaScript — think of it as a drop-in replacement for Node.js and npm. Follow the [official install guide](https://bun.com/docs/installation).
Expand Down
10 changes: 9 additions & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,21 @@ apps/

## 下载

从 [最新 release](https://github.com/deer-flow/llm-space/releases/latest) 下载 DMG —— 支持 macOS 的 Apple Silicon 和 Intel。有两个版本:
### macOS

从 [最新 release](https://github.com/deer-flow/llm-space/releases/latest) 下载 DMG —— 支持 Apple Silicon 和 Intel。有两个版本:

- **LLM Space** —— 使用系统 WebView。体积小(约 27 MB),内存和耗电更低。
- **LLM Space Performance** —— 内嵌渲染引擎(约 130 MB)。渲染在不同 macOS 版本上保持一致,性能通常更好。

装其中一个,或者两个都装。它们共享同一份 `~/.llm-space` 数据,所以切换版本后 thread 和设置都还在,两者也都会自动更新。

### Windows x64 Canary

Windows 10/11 x64 用户可以从 [Canary 预发布版本](https://github.com/deer-flow/llm-space/releases) 下载 `LLMSpace-*-windows-x64.zip`,解压后运行其中的 `LLM Space-Setup-canary.exe`。当前早期 Canary 尚未签名,因此 Microsoft Defender SmartScreen 可能会在启动前显示警告。该版本使用系统 WebView2 runtime;目前没有 Windows Performance/CEF 或原生 ARM64 构建。

Canary 已知限制:Windows 不会注册 `llm-space://` URL scheme,WebView2 在高 DPI 缩放下可能略显模糊,从 Explorer 拖入文件后应用也可能暂时失去焦点。要导入共享 thread,请复制其 HTTPS viewer URL,然后在应用中选择 **File → Import from Clipboard**。

## 安装

要从源码构建的话,你需要先安装 [Bun](https://bun.com)。Bun 是一个快速、一体化的 JavaScript runtime 和 package manager,可以理解为 Node.js 和 npm 的替代方案。请参考 [官方安装指南](https://bun.com/docs/installation)。
Expand Down
11 changes: 8 additions & 3 deletions apps/desktop/electrobun.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,19 @@ export default {
bundleCEF: false,
},
win: {
// Windows ships only the regular WebView2 edition for now.
bundleCEF: false,
icon: "icon.ico",
},
},
scripts: {
// Both run right before their respective codesign step. Workaround for
// electrobun#485 (x64-only, no-op elsewhere); see the script header.
postBuild: "scripts/fix-x64-headerpad.ts",
// The inner-app hook preserves the macOS x64 headerpad workaround and also
// embeds the Windows icon before the updater tarball is compressed. The
// wrapper hook remains macOS-only; Windows Setup.exe is finalized after
// packaging because Electrobun 1.18.1's compiled rcedit lookup is broken.
postBuild: "scripts/post-build.ts",
postWrap: "scripts/fix-x64-headerpad.ts",
postPackage: "scripts/post-package.ts",
},
release: {
// Burned into every shipped bundle — the updater fetches
Expand Down
Binary file added apps/desktop/icon.ico
Binary file not shown.
5 changes: 4 additions & 1 deletion apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"hmr": "vite --port 5173",
"build:view": "NODE_OPTIONS=--max-old-space-size=4096 vite build",
"build:canary": "bun run build:view && electrobun build --env=canary",
"build:stable": "bun run build:view && electrobun build --env=stable"
"build:stable": "bun run build:view && electrobun build --env=stable",
"verify:pack:win": "bun scripts/verify-windows-package.ts",
"smoke:win": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts/smoke-test-windows.ps1"
},
"dependencies": {
"@base-ui/react": "^1.6.0",
Expand Down Expand Up @@ -69,6 +71,7 @@
"autoprefixer": "^10.4.20",
"concurrently": "^9.1.0",
"postcss": "^8.5.3",
"rcedit": "^4.0.1",
"react-scan": "^0.5.7",
"tailwindcss": "^4.0.15",
"typescript": "^5.7.2",
Expand Down
61 changes: 61 additions & 0 deletions apps/desktop/scripts/post-build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { existsSync, renameSync, rmSync } from "node:fs";
import path from "node:path";

import rcedit from "rcedit";

import { compileWindowsGuiExecutable } from "./windows-csharp";
import {
patchWindowsExecutableFileToConsole,
patchWindowsExecutableFileToGui,
} from "./windows-executable";

const headerpad = Bun.spawnSync(
[process.execPath, path.join(import.meta.dir, "fix-x64-headerpad.ts")],
{
cwd: path.join(import.meta.dir, ".."),
env: process.env,
stdio: ["ignore", "inherit", "inherit"],
}
);
if (headerpad.exitCode !== 0) {
throw new Error(`fix-x64-headerpad exited with code ${headerpad.exitCode}`);
}

if (process.env.ELECTROBUN_OS === "win") {
const buildDirectory = _requiredEnv("ELECTROBUN_BUILD_DIR");
const appName = _requiredEnv("ELECTROBUN_APP_NAME");
const binDirectory = path.join(buildDirectory, appName, "bin");
const icon = path.resolve(import.meta.dir, "..", "icon.ico");
const launcher = path.join(binDirectory, "launcher.exe");
const coreLauncher = path.join(binDirectory, "launcher-core.exe");
const bundledBun = path.join(binDirectory, "bun.exe");
for (const binary of [launcher, bundledBun]) {
if (!existsSync(binary)) {
throw new Error(`Windows app binary not found: ${binary}`);
}
}

rmSync(coreLauncher, { force: true });
renameSync(launcher, coreLauncher);
compileWindowsGuiExecutable({
source: path.join(import.meta.dir, "windows-launcher.cs"),
output: launcher,
icon,
});

for (const binary of [launcher, coreLauncher, bundledBun]) {
await rcedit(binary, { icon });
}
patchWindowsExecutableFileToConsole(coreLauncher);
for (const binary of [launcher, bundledBun]) {
patchWindowsExecutableFileToGui(binary);
console.info(`Configured Windows GUI subsystem: ${path.basename(binary)}`);
}
console.info("Wrapped launcher-core.exe with a console-free launcher.exe");
}

function _requiredEnv(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Missing ${name} in Electrobun build hook.`);
return value;
}
Loading
Loading