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
6 changes: 3 additions & 3 deletions .github/workflows/ci-cli.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
name: CLI

# meebox CLI(cli/,独立 Go module)的门禁,与 Node/Nx 的 CI 分开:
# 路径过滤只能加在 workflow 的 on 层(不能按 job 过滤),故独立成一条流水线——仅当 cli/ 变更时才跑,
# 既隔离 Go 工具链、又省 CI 分钟。发布期的交叉编译 / 出包见 release.yml 的 cli job。
# Gate for the meebox CLI (cli/, standalone Go module), separate from the Node/Nx CI:
# path filters can only live at the workflow `on` level (not per-job), so this is its own pipeline — runs only when cli/ changes,
# both isolating the Go toolchain and saving CI minutes. For release-time cross-compile / packaging see the cli job in release.yml.
on:
push:
branches: [master]
Expand Down
122 changes: 61 additions & 61 deletions .github/workflows/release.yml

Large diffs are not rendered by default.

10 changes: 5 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ __pycache__/
# Electron / packaged binaries
release/

# 嵌入式 pr-agent 运行时(scripts/assemble-pragent-runtime.mjs 生成;几百 MB、
# 平台特定的下载+pip 产物,不入库;见 docs/modules/04-pragent-runtime.md)
# Embedded pr-agent runtime (generated by scripts/assemble-pragent-runtime.mjs; hundreds of MB,
# platform-specific download + pip output, not committed; see docs/arch/02-agent/05-pragent-runtime.md)
apps/desktop/vendor/

# 第三方声明全文(tools/gen-third-party-notices.mjs 生成;近万行许可证全文,不入库——
# 出包前生成,由 electron-builder 打入安装包 <resources>/THIRD-PARTY-NOTICES.md)
# Full third-party notices (generated by tools/gen-third-party-notices.mjs; ~10k lines of full license
# text, not committed — generated before packaging, bundled by electron-builder into the installer at <resources>/THIRD-PARTY-NOTICES.md)
THIRD-PARTY-NOTICES.md
*.exe
*.dmg
Expand Down Expand Up @@ -64,7 +64,7 @@ logs/
coverage/
.nyc_output/

# Claude Code 本地状态
# Claude Code local state
.claude/

.nx/polygraph
Expand Down
32 changes: 16 additions & 16 deletions apps/desktop/build-resources/after-pack.cjs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
// electron-builder afterPack 钩子 —— macOS 免费发布路线的 ad-hoc 签名。
// electron-builder afterPack hook — ad-hoc signing for the macOS free release route.
//
// 背景:Apple Silicon(arm64) 上任何 Mach-O 必须带有效签名才能执行;未签名的
// 嵌入式 python 解释器 / .dylib / .so 会在 spawn 时直接崩。没有 Apple Developer ID
// 时无法公证,但可以用 ad-hoc 身份(`codesign -s -`)免费签名让二进制能跑。
// Background: on Apple Silicon(arm64) any Mach-O must carry a valid signature to execute; unsigned
// embedded python interpreter / .dylib / .so crash directly on spawn. Without an Apple Developer ID
// notarization is impossible, but an ad-hoc identity (`codesign -s -`) can sign for free so the binaries run.
//
// 行为:
// - 仅在打 macOS 包时动作;win / linux 直接跳过。
// - 若检测到真实签名凭据(env),跳过 —— 交回 electron-builder 走正式签名 + 公证。
// - 否则对整个 .app 递归 ad-hoc 签名(含 Contents/Resources/pragent 下的嵌入式 python)。
// Behavior:
// - Only acts when packaging macOS; win / linux skip directly.
// - If real signing credentials (env) are detected, skip — handing back to electron-builder for proper signing + notarization.
// - Otherwise recursively ad-hoc sign the whole .app (including the embedded python under Contents/Resources/pragent).
//
// 注意:ad-hoc 签名只让二进制能运行,不去除 Gatekeeper 警告(仍需用户首次"仍要打开"
// 或走 Homebrew)。见 docs/mac-build.md。
// Note: ad-hoc signing only lets the binaries run, it does not remove the Gatekeeper warning (users still need "Open anyway" on first launch
// or go via Homebrew). See docs/mac-build.md.

const { execFileSync } = require('node:child_process');
const path = require('node:path');
Expand All @@ -19,26 +19,26 @@ const path = require('node:path');
exports.default = async function afterPack(context) {
if (context.electronPlatformName !== 'darwin') return;

// 有真证书 / 公证凭据时不做 ad-hoc,让 electron-builder 接管正式签名 + 公证
// With a real certificate / notarization credentials, skip ad-hoc and let electron-builder take over proper signing + notarization
const hasRealIdentity = Boolean(
process.env.CSC_LINK ||
process.env.CSC_NAME ||
process.env.APPLE_API_KEY ||
process.env.APPLE_ID,
);
if (hasRealIdentity) {
console.log('[after-pack] 检测到 Apple 签名凭据,跳过 ad-hoc(走正式签名 + 公证)');
console.log('[after-pack] Apple signing credentials detected, skipping ad-hoc (using proper signing + notarization)');
return;
}

const appName = `${context.packager.appInfo.productFilename}.app`;
const appPath = path.join(context.appOutDir, appName);
console.log(`[after-pack] ad-hoc 递归签名(免费路线,不公证): ${appPath}`);
console.log(`[after-pack] ad-hoc recursive signing (free route, not notarized): ${appPath}`);

// --force 覆盖既有签名;--deep 递归签 bundle 内嵌套代码(含嵌入式 python 的 Mach-O)。
// ad-hoc 身份为 "-"。若个别 .so 仍报签名无效,见 docs/mac-build.md §嵌入式 python 补签。
// --force overwrites the existing signature; --deep recursively signs nested code inside the bundle (including the embedded python Mach-O).
// The ad-hoc identity is "-". If an individual .so still reports an invalid signature, see docs/mac-build.md §embedded python re-signing.
execFileSync('codesign', ['--force', '--deep', '--sign', '-', appPath], {
stdio: 'inherit',
});
console.log('[after-pack] ad-hoc 签名完成');
console.log('[after-pack] ad-hoc signing done');
};
68 changes: 34 additions & 34 deletions apps/desktop/electron-builder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,29 @@ appId: com.huhamhire.code-meeseeks
productName: Code Meeseeks
copyright: Copyright © 2026 huhamhire

# npm workspaces hoist 下 electron-builder 无法从 apps/desktop/node_modules
# 推算出 electron 实际版本,必须显式声明(保持与 devDependencies 一致)。
# Under npm workspaces hoisting, electron-builder cannot infer electron's actual version
# from apps/desktop/node_modules, so it must be declared explicitly (keep in sync with devDependencies).
electronVersion: 42.3.0

# macOS 免费路线:对打出的 .app 做 ad-hoc 递归签名(arm64 必需,含嵌入式 python)。
# 有 Apple 签名凭据 env 时自动跳过,交回正式签名 + 公证。win/linux 无动作。
# macOS free route: ad-hoc recursive signing of the built .app (required on arm64, includes embedded python).
# Automatically skipped when Apple signing credentials env is present, handing back to proper signing + notarization. No-op on win/linux.
afterPack: build-resources/after-pack.cjs

# 不使用 electron 自动更新;声明 GitHub provider 仅为让 electron-builder 能算出更新通道
# (computeChannelNames),消除 CI 上「无法从 .git/config 探测仓库」导致的 null 崩溃。
# 实际发布由 workflow 的 softprops 上传,构建步骤用 --publish never 只生成本地元数据、不上传。
# Not using electron auto-update; the GitHub provider is declared only so electron-builder can compute the update channel
# (computeChannelNames), eliminating the null crash on CI caused by "cannot detect repository from .git/config".
# Actual release is uploaded by the workflow's softprops; the build step uses --publish never to only generate local metadata, no upload.
publish:
provider: github
owner: huhamhire
repo: code-meeseeks

directories:
output: release
# 指向已入库的 build-resources/:其中的 installer.nsh 会被 electron-builder **自动收录**
# (auto-include `${buildResources}/installer.nsh`,路径无歧义)。原默认 build/ 被 gitignore、未用。
# Point at the checked-in build-resources/: its installer.nsh is **auto-included** by electron-builder
# (auto-include `${buildResources}/installer.nsh`, path is unambiguous). The original default build/ is gitignored and unused.
buildResources: build-resources

# 入口 + 所有 bundle 产物。out/* 是 electron-vite build 的输出。
# Entry point + all bundle artifacts. out/* is the output of electron-vite build.
files:
- out/**/*
- package.json
Expand All @@ -33,8 +33,8 @@ files:
- '!**/*.test.{ts,tsx,js}'
- '!**/*.{md,markdown}'

# Workspace 模式下,第三方运行时依赖被 hoist 到根 node_modules。
# 让 electron-builder 也从那里抽 prod deps(pino / yaml / zod 等)。
# In workspace mode, third-party runtime dependencies are hoisted to the root node_modules.
# Let electron-builder pull prod deps (pino / yaml / zod etc.) from there too.
nodeGypRebuild: false
npmRebuild: false

Expand All @@ -44,28 +44,28 @@ asarUnpack:
- '**/node_modules/pino-roll/**'
- '**/node_modules/thread-stream/**'

# 嵌入式 pr-agent 运行时(见 ADR-0008):vendor/pragent → <resources>/pragent,
# main 的 resolveEmbeddedPython 打包态走 process.resourcesPath/pragent。extraResources
# 天然落在 asar 外(原生解释器 + .pyd/.dll 必须是真实文件,不能进 asar)。
# 由构建机宿主平台 prepare:pragent 组装,与所构建的目标平台一致(初版 Windows x64)。
# __pycache__ 排除以瘦身(首次启动会重新生成 .pyc)。
# Embedded pr-agent runtime (see ADR-0008): vendor/pragent → <resources>/pragent,
# main's resolveEmbeddedPython uses process.resourcesPath/pragent when packaged. extraResources
# naturally land outside the asar (the native interpreter + .pyd/.dll must be real files, cannot go into the asar).
# Assembled by prepare:pragent on the build machine's host platform, matching the target platform being built (initial version Windows x64).
# __pycache__ is excluded to slim down (first launch regenerates .pyc).
extraResources:
- from: vendor/pragent
to: pragent
filter:
- '**/*'
- '!**/__pycache__/**'
# 第三方声明随包内置(落到 <resources>/THIRD-PARTY-NOTICES.md);由 tools/gen-third-party-notices.mjs
# 在 electron-builder 之前生成(见 dist/pack 脚本与 release workflow)。仓库不入库该文件。
# Third-party notices bundled with the package (landing at <resources>/THIRD-PARTY-NOTICES.md); generated by tools/gen-third-party-notices.mjs
# before electron-builder (see the dist/pack scripts and release workflow). The repo does not check in this file.
- from: ../../THIRD-PARTY-NOTICES.md
to: THIRD-PARTY-NOTICES.md
# 启动闪屏 logo:assets 不进 asar / 不随 out 打包,单独 copy 到 <resources>/icon.png,
# 供 main 进程 createSplash 运行时读取并 base64 内联到 splash data URL。
# Startup splash logo: assets do not go into the asar / are not bundled with out, so copied separately to <resources>/icon.png,
# for the main process createSplash to read at runtime and inline as base64 into the splash data URL.
- from: ../../assets/icons/icon.png
to: icon.png

win:
# 图标源放在资源目录 assets/icons/(build/ 被 gitignore,不用它)。含 16/32/48/256。
# Icon source lives in the assets/icons/ resource directory (build/ is gitignored, not used). Contains 16/32/48/256.
icon: ../../assets/icons/icon.ico
target:
- target: nsis
Expand All @@ -74,17 +74,17 @@ win:
artifactName: code-meeseeks-${version}-win-${arch}.${ext}

mac:
# mac 专用图标:深色圆角底板 + 留边 glyph(透明异形图标在 macOS 会被系统垫白底)。
# 由 tools/icons/gen-mac-icon.py 从 icon.png 合成;给 ≥512 PNG,electron-builder 自动转 .icns。
# mac-specific icon: dark rounded backing + padded glyph (transparent non-square icons get a white backing from the system on macOS).
# Composited from icon.png by tools/icons/gen-mac-icon.py; supply a ≥512 PNG and electron-builder auto-converts to .icns.
icon: ../../assets/icons/icon-mac.png
gatekeeperAssess: false
# 免费路线(无 Apple Developer ID,不公证):ad-hoc 递归签名由 afterPack 完成
# (arm64 上 Mach-O 必须签名才能跑,含嵌入式 python)。有真证书 env 时 afterPack
# 自动跳过、交回 electron-builder 走正式签名 + 公证(届时再加 hardenedRuntime /
# entitlements / notarize,见 docs/mac-build.md + build-resources/entitlements.mac.plist)
# Free route (no Apple Developer ID, not notarized): ad-hoc recursive signing done by afterPack
# (on arm64 a Mach-O must be signed to run, includes embedded python). With real certificate env, afterPack
# skips automatically, handing back to electron-builder for proper signing + notarization (add hardenedRuntime /
# entitlements / notarize at that point, see docs/mac-build.md + build-resources/entitlements.mac.plist)
target:
- target: dmg
# 初版仅 arm64(见 ADR-0008);需要 Intel 时再加 x64
# Initial version arm64 only (see ADR-0008); add x64 when Intel is needed
arch:
- arm64
category: public.app-category.developer-tools
Expand All @@ -101,10 +101,10 @@ linux:

nsis:
oneClick: false
# per-machine 安装(所有用户 / Program Files)。electron-builder 据此定义 INSTALL_MODE_PER_ALL_USERS
# → 安装器清单 RequestExecutionLevel admin(installer.nsi:20-25)→ 双击即弹 UAC、提权运行,
# 避免 perMachine:false(asInvoker) 在已有 per-machine 安装时"按需提权失败→静默退出→打不开"。
# 升级也变成单一提权实例,customInit 绕过旧卸载器更稳。安装后的应用本体仍 asInvoker、普通启动。
# per-machine install (all users / Program Files). electron-builder accordingly defines INSTALL_MODE_PER_ALL_USERS
# → installer manifest RequestExecutionLevel admin (installer.nsi:20-25) → double-click prompts UAC and runs elevated,
# avoiding perMachine:false(asInvoker) "on-demand elevation fails → silent exit → won't open" when a per-machine install already exists.
# Upgrades also become a single elevated instance, and customInit bypassing the old uninstaller is more robust. The installed app itself stays asInvoker, normal launch.
perMachine: true
allowToChangeInstallationDirectory: true
# 自定义注入见 build-resources/installer.nsh —— 由 buildResources auto-include 自动收录,无需显式 include。
# For custom injection see build-resources/installer.nsh — auto-included via buildResources auto-include, no explicit include needed.
6 changes: 3 additions & 3 deletions apps/desktop/electron.vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { defineConfig, externalizeDepsPlugin } from 'electron-vite';
import react from '@vitejs/plugin-react';
import { resolve } from 'node:path';

// Workspace 内部包源码是 .ts,Node 无法直接 import;让 Vite 把它们 bundle 进主进程/preload,
// 外部第三方依赖(electron / pino / yaml / zod ...)继续 externalize 让 Node 在运行时解析。
// Workspace internal packages are .ts source that Node cannot import directly; let Vite bundle them into main/preload,
// while external third-party deps (electron / pino / yaml / zod ...) stay externalized for Node to resolve at runtime.
const internalPackages = [
'@meebox/shared',
'@meebox/ipc',
Expand Down Expand Up @@ -40,7 +40,7 @@ export default defineConfig({
},
renderer: {
root: resolve('src/renderer'),
// 渲染层引用仓库根 assets/(品牌图标等单一来源,避免拷贝重复二进制)
// Renderer references the repo root assets/ (single source for brand icons etc., avoiding duplicate binary copies)
resolve: {
alias: { '@assets': resolve('../../assets') },
},
Expand Down
Loading
Loading