feat(mac): add ARM64 and x64 distribution scripts and update README

- Introduced new distribution scripts for macOS targeting ARM64 and x64 architectures.
- Updated README to clarify download instructions for macOS Apple Silicon and Intel users.
- Enhanced CI workflow to support building for both architectures, improving release process.
This commit is contained in:
matt 2026-02-14 12:12:44 +09:00
parent 15e80406fa
commit 8729039698
4 changed files with 86 additions and 12 deletions

View file

@ -49,7 +49,17 @@ jobs:
release-mac: release-mac:
needs: build needs: build
runs-on: macos-14 strategy:
fail-fast: false
matrix:
include:
- arch: arm64
runner: macos-14
dist_command: pnpm dist:mac:arm64
- arch: x64
runner: macos-13
dist_command: pnpm dist:mac:x64
runs-on: ${{ matrix.runner }}
steps: steps:
- name: Checkout - name: Checkout
@ -83,16 +93,16 @@ jobs:
VERSION="${GITHUB_REF#refs/tags/v}" VERSION="${GITHUB_REF#refs/tags/v}"
pnpm pkg set version="$VERSION" pnpm pkg set version="$VERSION"
- name: Build app (macOS) - name: Build app (macOS ${{ matrix.arch }})
run: pnpm build run: pnpm build
- name: Verify packaged inputs (macOS) - name: Verify packaged inputs (macOS ${{ matrix.arch }})
run: | run: |
test -f dist-electron/main/index.cjs test -f dist-electron/main/index.cjs
test -f dist-electron/preload/index.js test -f dist-electron/preload/index.js
test -f out/renderer/index.html test -f out/renderer/index.html
- name: Package & Release (macOS) - name: Package & Release (macOS ${{ matrix.arch }})
env: env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.CSC_LINK }} CSC_LINK: ${{ secrets.CSC_LINK }}
@ -100,7 +110,7 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }} APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: pnpm dist:mac run: ${{ matrix.dist_command }}
release-win: release-win:
needs: build needs: build

View file

@ -52,7 +52,8 @@
| Platform | Download | Notes | | Platform | Download | Notes |
|----------|----------|-------| |----------|----------|-------|
| **macOS** (Apple Silicon) | [`.dmg`](https://github.com/matt1398/claude-devtools/releases/latest) | Drag to Applications. On first launch: right-click → Open | | **macOS** (Apple Silicon) | [`.dmg`](https://github.com/matt1398/claude-devtools/releases/latest) | Download the `arm64` asset. Drag to Applications. On first launch: right-click → Open |
| **macOS** (Intel) | [`.dmg`](https://github.com/matt1398/claude-devtools/releases/latest) | Download the `x64` asset. Drag to Applications. On first launch: right-click → Open |
| **Windows** | [`.exe`](https://github.com/matt1398/claude-devtools/releases/latest) | Standard installer. May trigger SmartScreen — click "More info" → "Run anyway" | | **Windows** | [`.exe`](https://github.com/matt1398/claude-devtools/releases/latest) | Standard installer. May trigger SmartScreen — click "More info" → "Run anyway" |
The app reads session logs from `~/.claude/` — the data is already on your machine. No setup, no API keys, no login. The app reads session logs from `~/.claude/` — the data is already on your machine. No setup, no API keys, no login.
@ -201,7 +202,8 @@ The app auto-discovers your Claude Code projects from `~/.claude/`.
#### Build for Distribution #### Build for Distribution
```bash ```bash
pnpm dist:mac # macOS (.dmg) pnpm dist:mac:arm64 # macOS Apple Silicon (.dmg)
pnpm dist:mac:x64 # macOS Intel (.dmg)
pnpm dist:win # Windows (.exe) pnpm dist:win # Windows (.exe)
pnpm dist # Both platforms pnpm dist # Both platforms
``` ```

View file

@ -19,6 +19,8 @@
"build": "electron-vite build", "build": "electron-vite build",
"dist": "electron-builder --mac --win --linux", "dist": "electron-builder --mac --win --linux",
"dist:mac": "electron-builder --mac --publish always", "dist:mac": "electron-builder --mac --publish always",
"dist:mac:arm64": "electron-builder --mac --arm64 --publish always",
"dist:mac:x64": "electron-builder --mac --x64 --publish always",
"dist:win": "electron-builder --win --publish always", "dist:win": "electron-builder --win --publish always",
"dist:linux": "electron-builder --linux --publish always", "dist:linux": "electron-builder --linux --publish always",
"preview": "electron-vite preview", "preview": "electron-vite preview",

View file

@ -435,25 +435,85 @@ export class SshConnectionManager extends EventEmitter {
} }
private async resolveRemoteProjectsPath(username: string): Promise<string> { private async resolveRemoteProjectsPath(username: string): Promise<string> {
// Try to resolve the remote home directory // Prefer remote $HOME when available, then fall back to common paths.
// SFTP doesn't have a direct "get home dir" call, so we try common paths const remoteHome = await this.resolveRemoteHomeDirectory();
const candidates = [ const candidates = [
...(remoteHome ? [path.posix.join(remoteHome, '.claude', 'projects')] : []),
`/home/${username}/.claude/projects`, `/home/${username}/.claude/projects`,
`/Users/${username}/.claude/projects`, `/Users/${username}/.claude/projects`,
`/root/.claude/projects`, `/root/.claude/projects`,
]; ];
for (const candidate of candidates) { for (const candidate of [...new Set(candidates)]) {
if (await this.provider.exists(candidate)) { if (await this.provider.exists(candidate)) {
return candidate; return candidate;
} }
} }
// Fallback: try to read from environment via realpath of ~ // Fallback to inferred home-based path when we could resolve $HOME.
// Default to Linux convention if (remoteHome) {
return path.posix.join(remoteHome, '.claude', 'projects');
}
// Final fallback: Linux convention.
return `/home/${username}/.claude/projects`; return `/home/${username}/.claude/projects`;
} }
/**
* Resolve remote user's home directory by querying `$HOME` over SSH.
*/
private async resolveRemoteHomeDirectory(): Promise<string | null> {
if (!this.client) {
return null;
}
try {
const home = await this.execRemoteCommand('printf %s "$HOME"');
const normalized = home.trim();
return normalized.startsWith('/') ? normalized : null;
} catch {
return null;
}
}
/**
* Execute a command on the connected SSH host and return stdout.
*/
private async execRemoteCommand(command: string): Promise<string> {
const client = this.client;
if (!client) {
throw new Error('SSH client is not connected');
}
return new Promise<string>((resolve, reject) => {
client.exec(command, (err, stream) => {
if (err) {
reject(err);
return;
}
let stdout = '';
let stderr = '';
stream.on('data', (chunk: Buffer | string) => {
stdout += chunk.toString();
});
stream.stderr.on('data', (chunk: Buffer | string) => {
stderr += chunk.toString();
});
stream.on('close', (code: number | null) => {
if (code === 0) {
resolve(stdout);
return;
}
reject(new Error(stderr.trim() || `Remote command failed with exit code ${code}`));
});
});
});
}
private handleDisconnect(): void { private handleDisconnect(): void {
if (this.state === 'disconnected') return; if (this.state === 'disconnected') return;