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