diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5efcd5b..486cc6f 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -560,6 +560,34 @@ jobs: cache-from: type=registry,ref=ghcr.io/${{ needs.build-base.outputs.repo_name }}:${{ matrix.variant }},mode=max cache-to: type=inline + # Publish .NET Tool to NuGet + publish-nuget: + name: Publish NuGet + needs: [test-cli-integration, test-shell-functions, test-airlock] + runs-on: ubuntu-24.04 + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + steps: + - name: Checkout repository + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup .NET + uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 + with: + global-json-file: global.json + + - name: Extract version from shell script + id: version + run: | + VERSION=$(grep -m1 "^# Version:" copilot_here.sh | sed 's/# Version: //') + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Pack as .NET tool + run: dotnet pack app/CopilotHere.csproj -c Release -p:PackAsTool=true -p:CopilotHereVersion=${{ steps.version.outputs.version }} --nologo + + - name: Push to NuGet + if: ${{ secrets.NUGET_API_KEY != '' }} + run: dotnet nuget push "app/bin/Release/*.nupkg" --api-key ${{ secrets.NUGET_API_KEY }} --source https://api.nuget.org/v3/index.json --skip-duplicate + # Summary job publish-summary: name: Publish Summary @@ -570,6 +598,7 @@ jobs: build-variants, build-compound-variants, release-cli, + publish-nuget, ] if: always() && (needs.build-base.result != 'skipped' || needs.release-cli.result != 'skipped') runs-on: ubuntu-24.04 @@ -612,6 +641,15 @@ jobs: echo "- linux-x64, linux-arm64" >> $GITHUB_STEP_SUMMARY echo "- osx-x64, osx-arm64" >> $GITHUB_STEP_SUMMARY echo "- win-x64, win-arm64" >> $GITHUB_STEP_SUMMARY + echo "" >> $GITHUB_STEP_SUMMARY + echo "**Package Managers:**" >> $GITHUB_STEP_SUMMARY + echo "- Homebrew tap updated" >> $GITHUB_STEP_SUMMARY + echo "- WinGet manifest submitted" >> $GITHUB_STEP_SUMMARY + fi + + if [[ "${{ needs.publish-nuget.result }}" == "success" ]]; then + echo "" >> $GITHUB_STEP_SUMMARY + echo "**.NET Tool:** Published to nuget.org" >> $GITHUB_STEP_SUMMARY fi # Build native CLI binaries for all platforms @@ -687,6 +725,11 @@ jobs: - name: Checkout repository uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + - name: Setup .NET + uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4 + with: + global-json-file: global.json + - name: Download all artifacts uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 with: @@ -789,3 +832,122 @@ jobs: draft: false prerelease: false make_latest: true + + # --- Homebrew tap update --- + - name: Compute SHA256 for Homebrew + id: sha256 + run: | + echo "osx_arm64=$(sha256sum release/copilot_here-osx-arm64.tar.gz | awk '{print $1}')" >> $GITHUB_OUTPUT + echo "osx_x64=$(sha256sum release/copilot_here-osx-x64.tar.gz | awk '{print $1}')" >> $GITHUB_OUTPUT + echo "linux_arm64=$(sha256sum release/copilot_here-linux-arm64.tar.gz | awk '{print $1}')" >> $GITHUB_OUTPUT + echo "linux_x64=$(sha256sum release/copilot_here-linux-x64.tar.gz | awk '{print $1}')" >> $GITHUB_OUTPUT + + - name: Update Homebrew formula + if: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY != '' }} + env: + HOMEBREW_TAP_DEPLOY_KEY: ${{ secrets.HOMEBREW_TAP_DEPLOY_KEY }} + run: | + TAG="cli-v${{ steps.version.outputs.version }}-${{ steps.version.outputs.short_sha }}" + VERSION="${{ steps.version.outputs.version }}" + + # Configure SSH for deploy key + mkdir -p ~/.ssh + echo "$HOMEBREW_TAP_DEPLOY_KEY" > ~/.ssh/homebrew_tap_key + chmod 600 ~/.ssh/homebrew_tap_key + ssh-keyscan github.com >> ~/.ssh/known_hosts 2>/dev/null + export GIT_SSH_COMMAND="ssh -i ~/.ssh/homebrew_tap_key -o StrictHostKeyChecking=no" + + # Clone the tap repo + git clone git@github.com:GordonBeeming/homebrew-tap.git /tmp/homebrew-tap + + # Generate the formula + cat > /tmp/homebrew-tap/Formula/copilot_here.rb << FORMULA_EOF + # typed: false + # frozen_string_literal: true + + class CopilotHere < Formula + desc "Run GitHub Copilot CLI in a sandboxed Docker container" + homepage "https://github.com/GordonBeeming/copilot_here" + version "$VERSION" + license "FSL-1.1-MIT" + + on_macos do + if Hardware::CPU.arm? + url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-osx-arm64.tar.gz" + sha256 "${{ steps.sha256.outputs.osx_arm64 }}" + else + url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-osx-x64.tar.gz" + sha256 "${{ steps.sha256.outputs.osx_x64 }}" + end + end + + on_linux do + if Hardware::CPU.arm? + url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-linux-arm64.tar.gz" + sha256 "${{ steps.sha256.outputs.linux_arm64 }}" + else + url "https://github.com/GordonBeeming/copilot_here/releases/download/$TAG/copilot_here-linux-x64.tar.gz" + sha256 "${{ steps.sha256.outputs.linux_x64 }}" + end + end + + depends_on "docker" => :recommended + + def install + bin.install "copilot_here" + end + + def caveats + <<~EOS + To enable the shell function wrapper, run: + copilot_here --install-shells + + Or manually source the shell script in your profile: + Bash/Zsh: source "\$(brew --prefix)/share/copilot_here/copilot_here.sh" + EOS + end + + test do + assert_match version.to_s, shell_output("\#{bin}/copilot_here --version") + end + end + FORMULA_EOF + + # Remove leading whitespace from heredoc + sed -i 's/^ //' /tmp/homebrew-tap/Formula/copilot_here.rb + + # Commit and push + cd /tmp/homebrew-tap + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Formula/copilot_here.rb + git commit -m "Update copilot_here to $VERSION" + git push + + # Clean up + rm -f ~/.ssh/homebrew_tap_key + + # --- WinGet manifest update --- + - name: Compute SHA256 for WinGet + id: winget_sha256 + run: | + echo "win_x64=$(sha256sum release/copilot_here-win-x64.zip | awk '{print $1}')" >> $GITHUB_OUTPUT + echo "win_arm64=$(sha256sum release/copilot_here-win-arm64.zip | awk '{print $1}')" >> $GITHUB_OUTPUT + + - name: Update WinGet manifest + if: ${{ secrets.WINGET_PAT != '' }} + env: + WINGET_PAT: ${{ secrets.WINGET_PAT }} + run: | + TAG="cli-v${{ steps.version.outputs.version }}-${{ steps.version.outputs.short_sha }}" + VERSION="${{ steps.version.outputs.version }}" + URL_X64="https://github.com/${{ github.repository }}/releases/download/$TAG/copilot_here-win-x64.zip" + URL_ARM64="https://github.com/${{ github.repository }}/releases/download/$TAG/copilot_here-win-arm64.zip" + + # Install wingetcreate as .NET tool + dotnet tool install --global wingetcreate + + wingetcreate update GordonBeeming.CopilotHere \ + --version "$VERSION" \ + --urls "$URL_X64" "$URL_ARM64" \ + --submit --token "$WINGET_PAT" diff --git a/LICENSE b/LICENSE index 7f583c5..7bd8acf 100644 --- a/LICENSE +++ b/LICENSE @@ -6,7 +6,7 @@ FSL-1.1-MIT ## Notice -Copyright 2025 Gordon Beeming +Copyright 2026 Gordon Beeming ## Terms and Conditions diff --git a/app/CopilotHere.csproj b/app/CopilotHere.csproj index e4612ba..f50032f 100644 --- a/app/CopilotHere.csproj +++ b/app/CopilotHere.csproj @@ -11,12 +11,24 @@ false - true + + copilot_here + copilot_here + Gordon Beeming + Run GitHub Copilot CLI in a sandboxed Docker container + LICENSE + https://github.com/GordonBeeming/copilot_here + https://github.com/GordonBeeming/copilot_here + copilot;docker;cli;github + README.md + + + true true - full - false - Size + full + false + Size @@ -37,6 +49,11 @@ + + + + + diff --git a/packaging/SETUP.md b/packaging/SETUP.md new file mode 100644 index 0000000..24a9bcf --- /dev/null +++ b/packaging/SETUP.md @@ -0,0 +1,87 @@ +# Package Manager Distribution Setup + +This document describes the external setup steps required to enable automated package manager distribution. + +## 1. NuGet (.NET Tool - Issue #50) + +The CI workflow publishes the CLI as a .NET global tool to nuget.org. + +### Setup steps + +- [ ] Create/verify a nuget.org account at https://www.nuget.org/ +- [ ] Generate an API key at https://www.nuget.org/account/apikeys + - Scope: **Push new packages and package versions** + - Glob pattern: `copilot_here` +- [ ] Add GitHub Actions secret on the `copilot_here` repo: + - Go to **Settings > Secrets and variables > Actions > New repository secret** + - Name: `NUGET_API_KEY` + - Value: the API key from nuget.org + +### Usage + +```bash +dotnet tool install -g copilot_here +copilot_here --version +``` + +--- + +## 2. Homebrew Tap (Issue #51) + +The CI workflow updates a Homebrew formula in a separate tap repository after each release. + +### Setup steps + +- [ ] Create a public repository: [`GordonBeeming/homebrew-tap`](https://github.com/GordonBeeming/homebrew-tap) +- [ ] Copy the formula template from `packaging/homebrew/Formula/copilot_here.rb` to the new repo at `Formula/copilot_here.rb` +- [ ] Generate an SSH deploy key pair: + ```bash + ssh-keygen -t ed25519 -C "homebrew-tap-deploy" -f homebrew_tap_key -N "" + ``` +- [ ] Add the **public** key (`homebrew_tap_key.pub`) as a deploy key on the `homebrew-tap` repo: + - Go to `homebrew-tap` repo **Settings > Deploy keys > Add deploy key** + - Title: `copilot_here CI` + - Check **Allow write access** +- [ ] Add the **private** key (`homebrew_tap_key`) as a GitHub Actions secret on the `copilot_here` repo: + - Name: `HOMEBREW_TAP_DEPLOY_KEY` + - Value: the full contents of the private key file + +### Usage + +```bash +brew tap gordonbeeming/tap +brew install copilot_here +copilot_here --version +``` + +--- + +## 3. WinGet (Issue #52) + +The CI workflow auto-submits manifest updates to the `microsoft/winget-pkgs` repository using `wingetcreate`. + +### Setup steps + +- [ ] Generate a Personal Access Token (classic) at https://github.com/settings/tokens + - Scope: `public_repo` (needs to submit PRs to `microsoft/winget-pkgs`) +- [ ] Add GitHub Actions secret on the `copilot_here` repo: + - Name: `WINGET_PAT` + - Value: the PAT +- [ ] **First submission note:** After code changes are merged and a release is created, the CI will auto-submit the first PR to `microsoft/winget-pkgs`. This initial PR requires manual review/approval by Microsoft maintainers (typically takes 1-3 days). Subsequent version updates are auto-approved. + +### Usage + +```powershell +winget install GordonBeeming.CopilotHere +copilot_here --version +``` + +--- + +## Summary of required GitHub secrets + +| Secret Name | Purpose | Where to get it | +|---|---|---| +| `NUGET_API_KEY` | Push .NET tool to nuget.org | https://www.nuget.org/account/apikeys | +| `HOMEBREW_TAP_DEPLOY_KEY` | Update Homebrew formula | SSH deploy key (write access) on `homebrew-tap` repo | +| `WINGET_PAT` | Submit WinGet manifest PRs | https://github.com/settings/tokens (scope: `public_repo`) | diff --git a/packaging/homebrew/Formula/copilot_here.rb b/packaging/homebrew/Formula/copilot_here.rb new file mode 100644 index 0000000..937fce7 --- /dev/null +++ b/packaging/homebrew/Formula/copilot_here.rb @@ -0,0 +1,49 @@ +# typed: false +# frozen_string_literal: true + +class CopilotHere < Formula + desc "Run GitHub Copilot CLI in a sandboxed Docker container" + homepage "https://github.com/GordonBeeming/copilot_here" + version "VERSION_PLACEHOLDER" + license "FSL-1.1-MIT" + + on_macos do + if Hardware::CPU.arm? + url "https://github.com/GordonBeeming/copilot_here/releases/download/TAG_PLACEHOLDER/copilot_here-osx-arm64.tar.gz" + sha256 "SHA256_OSX_ARM64_PLACEHOLDER" + else + url "https://github.com/GordonBeeming/copilot_here/releases/download/TAG_PLACEHOLDER/copilot_here-osx-x64.tar.gz" + sha256 "SHA256_OSX_X64_PLACEHOLDER" + end + end + + on_linux do + if Hardware::CPU.arm? + url "https://github.com/GordonBeeming/copilot_here/releases/download/TAG_PLACEHOLDER/copilot_here-linux-arm64.tar.gz" + sha256 "SHA256_LINUX_ARM64_PLACEHOLDER" + else + url "https://github.com/GordonBeeming/copilot_here/releases/download/TAG_PLACEHOLDER/copilot_here-linux-x64.tar.gz" + sha256 "SHA256_LINUX_X64_PLACEHOLDER" + end + end + + depends_on "docker" => :recommended + + def install + bin.install "copilot_here" + end + + def caveats + <<~EOS + To enable the shell function wrapper, run: + copilot_here --install-shells + + Or manually source the shell script in your profile: + Bash/Zsh: source "$(brew --prefix)/share/copilot_here/copilot_here.sh" + EOS + end + + test do + assert_match version.to_s, shell_output("#{bin}/copilot_here --version") + end +end diff --git a/packaging/winget/GordonBeeming.CopilotHere.installer.yaml b/packaging/winget/GordonBeeming.CopilotHere.installer.yaml new file mode 100644 index 0000000..5616372 --- /dev/null +++ b/packaging/winget/GordonBeeming.CopilotHere.installer.yaml @@ -0,0 +1,15 @@ +PackageIdentifier: GordonBeeming.CopilotHere +PackageVersion: 2026.02.19 +InstallerType: zip +NestedInstallerType: portable +NestedInstallerFiles: + - RelativeFilePath: copilot_here.exe +Installers: + - Architecture: x64 + InstallerUrl: https://github.com/GordonBeeming/copilot_here/releases/download/TAG_PLACEHOLDER/copilot_here-win-x64.zip + InstallerSha256: SHA256_WIN_X64_PLACEHOLDER + - Architecture: arm64 + InstallerUrl: https://github.com/GordonBeeming/copilot_here/releases/download/TAG_PLACEHOLDER/copilot_here-win-arm64.zip + InstallerSha256: SHA256_WIN_ARM64_PLACEHOLDER +ManifestType: installer +ManifestVersion: 1.6.0 diff --git a/packaging/winget/GordonBeeming.CopilotHere.locale.en-US.yaml b/packaging/winget/GordonBeeming.CopilotHere.locale.en-US.yaml new file mode 100644 index 0000000..ada0bee --- /dev/null +++ b/packaging/winget/GordonBeeming.CopilotHere.locale.en-US.yaml @@ -0,0 +1,10 @@ +PackageIdentifier: GordonBeeming.CopilotHere +PackageVersion: 2026.02.19 +PackageLocale: en-US +Publisher: Gordon Beeming +PackageName: copilot_here +License: FSL-1.1-MIT +ShortDescription: Run GitHub Copilot CLI in a sandboxed Docker container +PackageUrl: https://github.com/GordonBeeming/copilot_here +ManifestType: defaultLocale +ManifestVersion: 1.6.0 diff --git a/packaging/winget/GordonBeeming.CopilotHere.yaml b/packaging/winget/GordonBeeming.CopilotHere.yaml new file mode 100644 index 0000000..e92dbc0 --- /dev/null +++ b/packaging/winget/GordonBeeming.CopilotHere.yaml @@ -0,0 +1,5 @@ +PackageIdentifier: GordonBeeming.CopilotHere +PackageVersion: 2026.02.19 +DefaultLocale: en-US +ManifestType: version +ManifestVersion: 1.6.0