Skip to content

Commit 90b281a

Browse files
committed
fix app bundle issue
1 parent cb97573 commit 90b281a

4 files changed

Lines changed: 166 additions & 36 deletions

File tree

.github/workflows/build.yml

Lines changed: 118 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,91 @@ jobs:
301301
ls -la cli-bundle/
302302
du -sh cli-bundle/
303303
304-
- name: Update tauri.conf.json for CLI bundle
305-
if: steps.check.outputs.skip != 'true'
304+
- name: Sign CLI bundle binaries (macOS)
305+
if: steps.check.outputs.skip != 'true' && startsWith(matrix.platform, 'macos')
306+
env:
307+
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
308+
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
309+
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
310+
shell: bash
311+
run: |
312+
# Import certificate
313+
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
314+
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
315+
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
316+
317+
# Decode and save certificate
318+
echo -n "$APPLE_CERTIFICATE" | base64 --decode -o $CERTIFICATE_PATH
319+
320+
# Create temporary keychain
321+
security create-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
322+
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
323+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" $KEYCHAIN_PATH
324+
325+
# Import certificate to keychain
326+
security import $CERTIFICATE_PATH -P "$APPLE_CERTIFICATE_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN_PATH
327+
security list-keychain -d user -s $KEYCHAIN_PATH
328+
329+
# Sign all executable binaries in cli-bundle
330+
echo "Signing binaries in cli-bundle..."
331+
cd src-api/dist/cli-bundle
332+
333+
# Sign node binary first
334+
if [ -f "node" ]; then
335+
echo "Signing node..."
336+
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp node
337+
fi
338+
339+
# Sign ALL .dylib files (including @img/sharp-libvips-*)
340+
echo "Signing all .dylib files..."
341+
find . -name "*.dylib" -type f | while read -r file; do
342+
echo "Signing $file..."
343+
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp "$file" || true
344+
done
345+
346+
# Sign ALL .node files (including @img/sharp-*)
347+
echo "Signing all .node files..."
348+
find . -name "*.node" -type f | while read -r file; do
349+
echo "Signing $file..."
350+
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp "$file" || true
351+
done
352+
353+
# Sign ALL .so files
354+
echo "Signing all .so files..."
355+
find . -name "*.so" -type f | while read -r file; do
356+
echo "Signing $file..."
357+
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp "$file" || true
358+
done
359+
360+
# Sign ripgrep and other Mach-O executables
361+
echo "Signing Mach-O executable binaries..."
362+
find . -type f \( -name "rg" -o -name "node" -o -perm -u+x \) 2>/dev/null | while read -r file; do
363+
# Skip non-binary files
364+
case "$file" in
365+
*.js|*.json|*.md|*.txt|*.ts|*.mjs|*.cjs|*.map|*.yml|*.yaml) continue ;;
366+
esac
367+
# Check if it's a Mach-O binary
368+
if file "$file" 2>/dev/null | grep -q "Mach-O"; then
369+
echo "Signing Mach-O: $file..."
370+
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp "$file" || true
371+
fi
372+
done
373+
374+
cd ../../..
375+
376+
# Also sign the launcher scripts (they are executables)
377+
echo "Signing launcher scripts..."
378+
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp src-api/dist/claude
379+
codesign --force --options runtime --sign "$APPLE_SIGNING_IDENTITY" --timestamp src-api/dist/codex
380+
381+
echo "All binaries signed successfully"
382+
383+
# Cleanup
384+
security delete-keychain $KEYCHAIN_PATH
385+
rm -f $CERTIFICATE_PATH
386+
387+
- name: Update tauri.conf.json for CLI bundle (Unix)
388+
if: steps.check.outputs.skip != 'true' && matrix.platform != 'windows'
306389
shell: bash
307390
run: |
308391
node -e "
@@ -312,7 +395,7 @@ jobs:
312395
if (!config.bundle.externalBin) config.bundle.externalBin = [];
313396
if (!config.bundle.resources) config.bundle.resources = [];
314397
315-
// Add CLI launchers
398+
// Add CLI launchers (Unix only - they are shell scripts)
316399
if (!config.bundle.externalBin.includes('../src-api/dist/claude')) {
317400
config.bundle.externalBin.unshift('../src-api/dist/claude');
318401
}
@@ -330,7 +413,38 @@ jobs:
330413
}
331414
332415
fs.writeFileSync('src-tauri/tauri.conf.json', JSON.stringify(config, null, 2));
333-
console.log('Updated tauri.conf.json with CLI bundle config');
416+
console.log('Updated tauri.conf.json with CLI bundle config (Unix)');
417+
"
418+
419+
- name: Update tauri.conf.json for CLI bundle (Windows)
420+
if: steps.check.outputs.skip != 'true' && matrix.platform == 'windows'
421+
shell: bash
422+
run: |
423+
node -e "
424+
const fs = require('fs');
425+
const config = JSON.parse(fs.readFileSync('src-tauri/tauri.conf.json', 'utf8'));
426+
427+
if (!config.bundle.resources) config.bundle.resources = [];
428+
429+
// Windows: Don't add to externalBin (Tauri expects .exe files)
430+
// Remove any existing claude/codex from externalBin
431+
if (config.bundle.externalBin) {
432+
config.bundle.externalBin = config.bundle.externalBin.filter(bin =>
433+
!bin.includes('claude') && !bin.includes('codex')
434+
);
435+
}
436+
437+
// Add cli-bundle as resource (backend will call node directly)
438+
const cliResource = '../src-api/dist/cli-bundle/**/*';
439+
if (!config.bundle.resources.includes(cliResource)) {
440+
config.bundle.resources = config.bundle.resources.filter(r =>
441+
!r.includes('claude-bundle') && !r.includes('codex-bundle') && !r.includes('cli-bundle')
442+
);
443+
config.bundle.resources.push(cliResource);
444+
}
445+
446+
fs.writeFileSync('src-tauri/tauri.conf.json', JSON.stringify(config, null, 2));
447+
console.log('Updated tauri.conf.json with CLI bundle config (Windows - resources only)');
334448
"
335449
336450
- name: Build frontend

src-api/src/app/api/health.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ function checkSidecarClaudeCode(): boolean {
190190
join(execDir, 'cli-bundle'),
191191
join(execDir, '..', 'Resources', 'cli-bundle'),
192192
join(execDir, '..', 'Resources', '_up_', 'src-api', 'dist', 'cli-bundle'),
193+
// Windows: Tauri places resources relative to exe with preserved path structure
194+
join(execDir, '_up_', 'src-api', 'dist', 'cli-bundle'),
193195
];
194196

195197
for (const bundleDir of bundleLocations) {

src-api/src/extensions/agent/claude/index.ts

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -198,26 +198,29 @@ function getSidecarClaudeCodePath(): string | undefined {
198198
// Get the directory containing the launcher
199199
const launcherDir = dirname(launcherPath);
200200

201-
// Check if claude-bundle directory exists alongside the launcher
202-
const bundleDir = join(launcherDir, 'claude-bundle');
203-
const claudeCliPath = join(
204-
bundleDir,
205-
'node_modules',
206-
'@anthropic-ai',
207-
'claude-code',
208-
'cli.js'
209-
);
210-
const nodeBinPath = join(bundleDir, os === 'win32' ? 'node.exe' : 'node');
201+
// Check if cli-bundle or claude-bundle directory exists alongside the launcher
202+
const bundleNames = ['cli-bundle', 'claude-bundle'];
203+
for (const bundleName of bundleNames) {
204+
const bundleDir = join(launcherDir, bundleName);
205+
const claudeCliPath = join(
206+
bundleDir,
207+
'node_modules',
208+
'@anthropic-ai',
209+
'claude-code',
210+
'cli.js'
211+
);
212+
const nodeBinPath = join(bundleDir, os === 'win32' ? 'node.exe' : 'node');
211213

212-
if (
213-
existsSync(bundleDir) &&
214-
existsSync(claudeCliPath) &&
215-
existsSync(nodeBinPath)
216-
) {
217-
console.log(`[Claude] Found bundled Claude Code at: ${launcherPath}`);
218-
console.log(`[Claude] Bundle directory: ${bundleDir}`);
219-
console.log(`[Claude] Node.js binary: ${nodeBinPath}`);
220-
return launcherPath;
214+
if (
215+
existsSync(bundleDir) &&
216+
existsSync(claudeCliPath) &&
217+
existsSync(nodeBinPath)
218+
) {
219+
console.log(`[Claude] Found bundled Claude Code at: ${launcherPath}`);
220+
console.log(`[Claude] Bundle directory: ${bundleDir}`);
221+
console.log(`[Claude] Node.js binary: ${nodeBinPath}`);
222+
return launcherPath;
223+
}
221224
}
222225

223226
// If no bundle dir but launcher exists, it might be a standalone binary
@@ -227,8 +230,16 @@ function getSidecarClaudeCodePath(): string | undefined {
227230
}
228231
}
229232

230-
// Also try direct check for claude-bundle in common locations
233+
// Also try direct check for cli-bundle/claude-bundle in common locations
231234
const bundleLocations = [
235+
// New unified cli-bundle structure
236+
join(execDir, 'cli-bundle'),
237+
join(execDir, '..', 'Resources', 'cli-bundle'),
238+
// macOS: Tauri places resources with preserved path structure
239+
join(execDir, '..', 'Resources', '_up_', 'src-api', 'dist', 'cli-bundle'),
240+
// Windows: Tauri places resources relative to exe with preserved path structure
241+
join(execDir, '_up_', 'src-api', 'dist', 'cli-bundle'),
242+
// Legacy claude-bundle for backward compatibility
232243
join(execDir, 'claude-bundle'),
233244
join(execDir, '..', 'Resources', 'claude-bundle'),
234245
];
@@ -247,13 +258,20 @@ function getSidecarClaudeCodePath(): string | undefined {
247258

248259
if (existsSync(claudeCliPath) && existsSync(nodeBinPath)) {
249260
// Create a path that points to using the bundled node to run claude
250-
// The launcher script should be in the parent directory
251-
const launcherPath = join(dirname(bundleDir), claudeName);
252-
if (existsSync(launcherPath)) {
253-
console.log(
254-
`[Claude] Found bundled Claude Code launcher at: ${launcherPath}`
255-
);
256-
return launcherPath;
261+
// The launcher script should be in the parent directory or a few levels up
262+
const possibleLauncherDirs = [
263+
dirname(bundleDir), // Direct parent
264+
join(dirname(bundleDir), '..', '..', '..'), // For _up_/src-api/dist/cli-bundle structure
265+
];
266+
267+
for (const launcherDir of possibleLauncherDirs) {
268+
const launcherPath = join(launcherDir, claudeName);
269+
if (existsSync(launcherPath)) {
270+
console.log(
271+
`[Claude] Found bundled Claude Code launcher at: ${launcherPath}`
272+
);
273+
return launcherPath;
274+
}
257275
}
258276

259277
// If no launcher, we can still return the path to use bundled node directly

src-tauri/tauri.conf.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,11 @@
3232
"icons/icon.ico"
3333
],
3434
"externalBin": [
35-
"../src-api/dist/codex",
36-
"../src-api/dist/claude",
3735
"../src-api/dist/workany-api"
3836
],
3937
"macOS": {
4038
"entitlements": "entitlements.plist"
4139
},
42-
"resources": [
43-
"../src-api/dist/cli-bundle/**/*"
44-
]
40+
"resources": []
4541
}
4642
}

0 commit comments

Comments
 (0)