Skip to content

Commit af249a0

Browse files
committed
Bundle copilot CLI binary for Mac Catalyst builds
The SDK's MSBuild targets skip maccatalyst-arm64 RID (only maps standard osx-arm64/win-x64/etc.), so the copilot binary was never downloaded or bundled. Users without copilot installed via npm would get 'CLI not found'. Fix: - Add MSBuild targets to map maccatalyst RIDs to darwin npm packages - Copy the downloaded binary into the .app bundle's MonoBundle - Add fallback chain in ResolveCopilotCliPath(): bundled → MonoBundle → system homebrew/npm → bare PATH
1 parent 86c6ddb commit af249a0

File tree

2 files changed

+95
-9
lines changed

2 files changed

+95
-9
lines changed

PolyPilot/PolyPilot.csproj

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,53 @@
8686
<TrimmerRootAssembly Include="GitHub.Copilot.SDK" />
8787
</ItemGroup>
8888

89+
<!-- The SDK only maps standard RIDs (osx-arm64, etc.) to download the copilot CLI.
90+
Mac Catalyst uses maccatalyst-arm64 which the SDK doesn't recognize, so the
91+
download is silently skipped. Map it to the darwin-arm64 npm package. -->
92+
<Target Name="_FixCopilotRidForMacCatalyst" BeforeTargets="_DownloadCopilotCli">
93+
<PropertyGroup Condition="'$(_CopilotPlatform)' == '' And $(_CopilotRid.StartsWith('maccatalyst-arm64'))">
94+
<_CopilotPlatform>darwin-arm64</_CopilotPlatform>
95+
<_CopilotBinary>copilot</_CopilotBinary>
96+
<_CopilotOriginalRid>$(_CopilotRid)</_CopilotOriginalRid>
97+
<_CopilotRid>osx-arm64</_CopilotRid>
98+
</PropertyGroup>
99+
<PropertyGroup Condition="'$(_CopilotPlatform)' == '' And '$(_CopilotRid)' == 'maccatalyst-x64'">
100+
<_CopilotPlatform>darwin-x64</_CopilotPlatform>
101+
<_CopilotBinary>copilot</_CopilotBinary>
102+
<_CopilotOriginalRid>$(_CopilotRid)</_CopilotOriginalRid>
103+
<_CopilotRid>osx-x64</_CopilotRid>
104+
</PropertyGroup>
105+
</Target>
106+
107+
<!-- The SDK at runtime uses RuntimeInformation.RuntimeIdentifier (maccatalyst-arm64)
108+
to locate the binary, so also copy it to that path. -->
109+
<Target Name="_CopyCopilotCliForMacCatalyst" AfterTargets="_CopyCopilotCliToOutput"
110+
Condition="'$(_CopilotOriginalRid)' != ''">
111+
<PropertyGroup>
112+
<_CopilotMacCatalystOutputDir>$(OutDir)runtimes/$(_CopilotOriginalRid)/native</_CopilotMacCatalystOutputDir>
113+
<_CopilotCacheDir>$(IntermediateOutputPath)copilot-cli/$(CopilotCliVersion)/$(_CopilotPlatform)</_CopilotCacheDir>
114+
</PropertyGroup>
115+
<MakeDir Directories="$(_CopilotMacCatalystOutputDir)" />
116+
<Copy SourceFiles="$(_CopilotCacheDir)/$(_CopilotBinary)"
117+
DestinationFolder="$(_CopilotMacCatalystOutputDir)"
118+
SkipUnchangedFiles="true" />
119+
</Target>
120+
121+
<!-- Ensure the copilot CLI binary is included in the Mac Catalyst .app bundle.
122+
MAUI flattens everything into MonoBundle, so runtimes/ paths don't work.
123+
Copy the binary directly into the .app bundle's MonoBundle. -->
124+
<Target Name="_IncludeCopilotCliInBundle" AfterTargets="Build"
125+
Condition="'$(_CopilotOriginalRid)' != ''">
126+
<PropertyGroup>
127+
<_CopilotCacheDir>$(IntermediateOutputPath)copilot-cli/$(CopilotCliVersion)/$(_CopilotPlatform)</_CopilotCacheDir>
128+
<_AppBundleMonoBundle>$(OutDir)PolyPilot.app/Contents/MonoBundle</_AppBundleMonoBundle>
129+
</PropertyGroup>
130+
<Copy SourceFiles="$(_CopilotCacheDir)/$(_CopilotBinary)"
131+
DestinationFolder="$(_AppBundleMonoBundle)"
132+
SkipUnchangedFiles="true"
133+
Condition="Exists('$(_AppBundleMonoBundle)')" />
134+
<Message Importance="high" Text="Copied copilot CLI binary to app bundle MonoBundle"
135+
Condition="Exists('$(_AppBundleMonoBundle)')" />
136+
</Target>
137+
89138
</Project>

PolyPilot/Services/CopilotService.cs

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -379,15 +379,14 @@ private CopilotClient CreateClient(ConnectionSettings settings)
379379
// Remote mode is handled by InitializeRemoteAsync, not here
380380
var options = new CopilotClientOptions { Cwd = ProjectDir };
381381

382-
// The SDK resolves a bundled CLI path internally via GetBundledCliPath().
383-
// If the bundled binary doesn't exist (e.g. no runtime package installed),
384-
// fall back to the system-installed 'copilot' on PATH.
385-
var bundledPath = GetBundledCliPath();
386-
if (bundledPath == null || !File.Exists(bundledPath))
387-
{
388-
Debug($"Bundled CLI not found, falling back to system 'copilot'");
389-
options.CliPath = "copilot";
390-
}
382+
// Resolve the copilot CLI path with fallback chain:
383+
// 1. SDK bundled path (runtimes/{rid}/native/copilot)
384+
// 2. MonoBundle/copilot (MAUI flattens runtimes/ into MonoBundle)
385+
// 3. System-installed native binary (homebrew/npm)
386+
// 4. Bare "copilot" on PATH
387+
var cliPath = ResolveCopilotCliPath();
388+
if (cliPath != null)
389+
options.CliPath = cliPath;
391390

392391
// Pass additional MCP server configs via CLI args.
393392
// The CLI auto-reads ~/.copilot/mcp-config.json, but mcp-servers.json
@@ -399,6 +398,44 @@ private CopilotClient CreateClient(ConnectionSettings settings)
399398
return new CopilotClient(options);
400399
}
401400

401+
/// <summary>
402+
/// Resolves the copilot CLI path with fallback chain.
403+
/// </summary>
404+
private static string? ResolveCopilotCliPath()
405+
{
406+
// 1. SDK bundled path (runtimes/{rid}/native/copilot)
407+
var bundledPath = GetBundledCliPath();
408+
if (bundledPath != null && File.Exists(bundledPath))
409+
return bundledPath;
410+
411+
// 2. MonoBundle/copilot (MAUI flattens runtimes/ into MonoBundle on Mac Catalyst)
412+
try
413+
{
414+
var assemblyDir = Path.GetDirectoryName(typeof(CopilotClient).Assembly.Location);
415+
if (assemblyDir != null)
416+
{
417+
var monoBundlePath = Path.Combine(assemblyDir, "copilot");
418+
if (File.Exists(monoBundlePath))
419+
return monoBundlePath;
420+
}
421+
}
422+
catch { }
423+
424+
// 3. System-installed native binary (homebrew/npm)
425+
var systemPaths = new[]
426+
{
427+
"/opt/homebrew/lib/node_modules/@github/copilot/node_modules/@github/copilot-darwin-arm64/copilot",
428+
"/usr/local/lib/node_modules/@github/copilot/node_modules/@github/copilot-darwin-arm64/copilot",
429+
};
430+
foreach (var path in systemPaths)
431+
{
432+
if (File.Exists(path)) return path;
433+
}
434+
435+
// 4. Bare "copilot" — relies on PATH
436+
return null;
437+
}
438+
402439
/// <summary>
403440
/// Resolves the path where the SDK expects a bundled copilot binary.
404441
/// Pattern: {assembly-dir}/runtimes/{rid}/native/copilot

0 commit comments

Comments
 (0)