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:
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:
- name: Checkout
@ -83,16 +93,16 @@ jobs:
VERSION="${GITHUB_REF#refs/tags/v}"
pnpm pkg set version="$VERSION"
- name: Build app (macOS)
- name: Build app (macOS ${{ matrix.arch }})
run: pnpm build
- name: Verify packaged inputs (macOS)
- name: Verify packaged inputs (macOS ${{ matrix.arch }})
run: |
test -f dist-electron/main/index.cjs
test -f dist-electron/preload/index.js
test -f out/renderer/index.html
- name: Package & Release (macOS)
- name: Package & Release (macOS ${{ matrix.arch }})
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CSC_LINK: ${{ secrets.CSC_LINK }}
@ -100,7 +110,7 @@ jobs:
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
run: pnpm dist:mac
run: ${{ matrix.dist_command }}
release-win:
needs: build

View file

@ -52,7 +52,8 @@
| 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" |
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
```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 # Both platforms
```

View file

@ -19,6 +19,8 @@
"build": "electron-vite build",
"dist": "electron-builder --mac --win --linux",
"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:linux": "electron-builder --linux --publish always",
"preview": "electron-vite preview",

View file

@ -435,25 +435,85 @@ export class SshConnectionManager extends EventEmitter {
}
private async resolveRemoteProjectsPath(username: string): Promise<string> {
// Try to resolve the remote home directory
// SFTP doesn't have a direct "get home dir" call, so we try common paths
// Prefer remote $HOME when available, then fall back to common paths.
const remoteHome = await this.resolveRemoteHomeDirectory();
const candidates = [
...(remoteHome ? [path.posix.join(remoteHome, '.claude', 'projects')] : []),
`/home/${username}/.claude/projects`,
`/Users/${username}/.claude/projects`,
`/root/.claude/projects`,
];
for (const candidate of candidates) {
for (const candidate of [...new Set(candidates)]) {
if (await this.provider.exists(candidate)) {
return candidate;
}
}
// Fallback: try to read from environment via realpath of ~
// Default to Linux convention
// Fallback to inferred home-based path when we could resolve $HOME.
if (remoteHome) {
return path.posix.join(remoteHome, '.claude', 'projects');
}
// Final fallback: Linux convention.
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 {
if (this.state === 'disconnected') return;