feat(distribution): binary release pipeline + brew + winget
- .github/workflows/release-cli.yml: build self-contained binaries via `bun build --compile` for darwin/linux/windows × x64/arm64 on every cli-v* tag, attach to GitHub Release with SHA256SUMS, auto-bump the homebrew tap on non-prerelease versions. - packaging/homebrew/claudemesh.rb.template: formula template for the homebrew-claudemesh tap. - packaging/winget/claudemesh.yaml.template: winget manifest template. - /install script now detects absence of Node and downloads the platform-appropriate binary from the GitHub Release, installs to ~/.claudemesh/bin, and shims into ~/.local/bin — zero Node required. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
112
.github/workflows/release-cli.yml
vendored
Normal file
112
.github/workflows/release-cli.yml
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
name: Release CLI binaries
|
||||
|
||||
# Fires on any push of a tag shaped like `cli-v1.2.3` (prerelease `-alpha.N` OK).
|
||||
# Builds self-contained `bun build --compile` binaries for darwin/linux/win
|
||||
# (x64 + arm64) and attaches them to a GitHub Release. The `install.sh`
|
||||
# fallback path curls these when Node isn't available.
|
||||
#
|
||||
# Publishing to npm is still a manual step (pnpm publish from apps/cli-v2) —
|
||||
# this workflow only handles binary distribution.
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "cli-v*"
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag to build (e.g. cli-v1.0.0-alpha.28)"
|
||||
required: true
|
||||
|
||||
permissions:
|
||||
contents: write # to upload release assets
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: ${{ matrix.target }}
|
||||
runs-on: ${{ matrix.runner }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- { target: darwin-x64, bun_target: bun-darwin-x64, runner: macos-latest, ext: "" }
|
||||
- { target: darwin-arm64, bun_target: bun-darwin-arm64, runner: macos-latest, ext: "" }
|
||||
- { target: linux-x64, bun_target: bun-linux-x64, runner: ubuntu-latest, ext: "" }
|
||||
- { target: linux-arm64, bun_target: bun-linux-arm64, runner: ubuntu-latest, ext: "" }
|
||||
- { target: windows-x64, bun_target: bun-windows-x64, runner: windows-latest, ext: ".exe" }
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: oven-sh/setup-bun@v2
|
||||
with:
|
||||
bun-version: "1.2"
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
with:
|
||||
version: 10
|
||||
|
||||
- name: Install workspace deps
|
||||
run: pnpm install --frozen-lockfile --ignore-scripts
|
||||
|
||||
- name: Compile binary
|
||||
working-directory: apps/cli-v2
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p dist/bin
|
||||
bun build --compile --minify \
|
||||
--target=${{ matrix.bun_target }} \
|
||||
src/entrypoints/cli.ts \
|
||||
--outfile dist/bin/claudemesh-${{ matrix.target }}${{ matrix.ext }}
|
||||
|
||||
- name: Smoke test (non-Windows)
|
||||
if: matrix.target != 'windows-x64'
|
||||
working-directory: apps/cli-v2
|
||||
run: |
|
||||
./dist/bin/claudemesh-${{ matrix.target }} --version
|
||||
./dist/bin/claudemesh-${{ matrix.target }} --help | head -5
|
||||
|
||||
- name: Upload artefact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: claudemesh-${{ matrix.target }}
|
||||
path: apps/cli-v2/dist/bin/claudemesh-${{ matrix.target }}${{ matrix.ext }}
|
||||
|
||||
release:
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: artifacts
|
||||
|
||||
- name: Stage binaries
|
||||
run: |
|
||||
mkdir -p release
|
||||
find artifacts -type f -exec cp {} release/ \;
|
||||
cd release && sha256sum claudemesh-* > SHA256SUMS
|
||||
|
||||
- name: Publish release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ github.ref_name }}
|
||||
files: |
|
||||
release/claudemesh-*
|
||||
release/SHA256SUMS
|
||||
generate_release_notes: true
|
||||
fail_on_unmatched_files: true
|
||||
|
||||
update-homebrew:
|
||||
needs: release
|
||||
runs-on: macos-latest
|
||||
if: github.event_name == 'push' && !contains(github.ref_name, 'alpha')
|
||||
steps:
|
||||
- name: Bump Homebrew tap formula
|
||||
env:
|
||||
HOMEBREW_GITHUB_API_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
|
||||
run: |
|
||||
brew tap alezmad/claudemesh || true
|
||||
brew bump-formula-pr --no-browse --no-fork \
|
||||
--tag "${{ github.ref_name }}" \
|
||||
--revision "${{ github.sha }}" \
|
||||
alezmad/claudemesh/claudemesh || echo "formula bump skipped (no tap yet)"
|
||||
@@ -30,41 +30,86 @@ say "\${BOLD}claudemesh-cli installer\${RESET}"
|
||||
say "$(printf '%.0s─' {1..40})"
|
||||
|
||||
# --- preflight ------------------------------------------------------
|
||||
# Prefer npm when Node 20+ is present. Otherwise fall back to a
|
||||
# self-contained binary download from GitHub Releases (installs to
|
||||
# ~/.claudemesh/bin and adds a shim at ~/.local/bin/claudemesh).
|
||||
|
||||
if ! command -v node >/dev/null 2>&1; then
|
||||
err "Node.js is not installed."
|
||||
say " Install Node.js 20 or newer: \${BOLD}https://nodejs.org\${RESET}"
|
||||
say " Or via nvm: \${DIM}curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\${RESET}"
|
||||
detect_os() {
|
||||
case "$(uname -s)" in
|
||||
Darwin) echo darwin ;;
|
||||
Linux) echo linux ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
detect_arch() {
|
||||
case "$(uname -m)" in
|
||||
x86_64|amd64) echo x64 ;;
|
||||
arm64|aarch64) echo arm64 ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
install_via_binary() {
|
||||
local os arch url target dir shim
|
||||
os=$(detect_os); arch=$(detect_arch)
|
||||
if [ -z "$os" ] || [ -z "$arch" ]; then
|
||||
err "No precompiled binary for $(uname -s)/$(uname -m). Install Node 20+ or build from source."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
dir="\${HOME}/.claudemesh/bin"
|
||||
mkdir -p "$dir"
|
||||
target="\${dir}/claudemesh"
|
||||
url="https://github.com/alezmad/claudemesh/releases/latest/download/claudemesh-\${os}-\${arch}"
|
||||
say "Downloading \${BOLD}\${url}\${RESET}…"
|
||||
if ! curl -fsSL "$url" -o "$target"; then
|
||||
err "Download failed. Falling back to npm."
|
||||
return 1
|
||||
fi
|
||||
chmod +x "$target"
|
||||
shim="\${HOME}/.local/bin/claudemesh"
|
||||
mkdir -p "\${HOME}/.local/bin"
|
||||
printf '#!/bin/sh\\nexec "%s" "$@"\\n' "$target" > "$shim"
|
||||
chmod +x "$shim"
|
||||
ok "claudemesh binary installed → \${target}"
|
||||
case ":$PATH:" in
|
||||
*":\${HOME}/.local/bin:"*) : ;;
|
||||
*)
|
||||
say ""
|
||||
say "\${BOLD}Add \${HOME}/.local/bin to PATH\${RESET} (add to your shell rc):"
|
||||
say " export PATH=\\"\${HOME}/.local/bin:\$PATH\\""
|
||||
;;
|
||||
esac
|
||||
return 0
|
||||
}
|
||||
|
||||
NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]")
|
||||
if [ "$NODE_MAJOR" -lt 20 ]; then
|
||||
err "Node.js $(node -v) is too old — claudemesh-cli needs >= 20."
|
||||
say " Upgrade: \${BOLD}https://nodejs.org\${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
ok "Node.js $(node -v)"
|
||||
|
||||
if ! command -v npm >/dev/null 2>&1; then
|
||||
err "npm is not installed (usually ships with Node)."
|
||||
exit 1
|
||||
fi
|
||||
ok "npm $(npm -v)"
|
||||
|
||||
# --- install --------------------------------------------------------
|
||||
|
||||
say ""
|
||||
say "Installing \${BOLD}claudemesh-cli\${RESET} from npm…"
|
||||
if ! npm install -g claudemesh-cli; then
|
||||
install_via_npm() {
|
||||
ok "Node.js $(node -v)"
|
||||
ok "npm $(npm -v)"
|
||||
say ""
|
||||
say "Installing \${BOLD}claudemesh-cli\${RESET} from npm…"
|
||||
if ! npm install -g claudemesh-cli; then
|
||||
err "npm install failed."
|
||||
say " If this is a permissions error on macOS/Linux, try:"
|
||||
say " \${DIM}sudo npm install -g claudemesh-cli\${RESET}"
|
||||
say " or configure npm to use a user-owned prefix:"
|
||||
say " \${DIM}https://docs.npmjs.com/resolving-eacces-permissions-errors\${RESET}"
|
||||
exit 1
|
||||
fi
|
||||
ok "claudemesh-cli installed ($(claudemesh --version))"
|
||||
}
|
||||
|
||||
if command -v node >/dev/null 2>&1; then
|
||||
NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]" 2>/dev/null || echo 0)
|
||||
if [ "$NODE_MAJOR" -ge 20 ] && command -v npm >/dev/null 2>&1; then
|
||||
install_via_npm
|
||||
else
|
||||
say "Node.js < 20 or no npm — using standalone binary."
|
||||
install_via_binary || install_via_npm
|
||||
fi
|
||||
else
|
||||
say "Node.js not detected — installing standalone binary (no Node required)."
|
||||
install_via_binary
|
||||
fi
|
||||
ok "claudemesh-cli installed ($(claudemesh --version))"
|
||||
|
||||
# --- register MCP + hooks ------------------------------------------
|
||||
|
||||
|
||||
54
packaging/homebrew/claudemesh.rb.template
Normal file
54
packaging/homebrew/claudemesh.rb.template
Normal file
@@ -0,0 +1,54 @@
|
||||
# Homebrew formula template — lives in the `alezmad/homebrew-claudemesh` tap.
|
||||
#
|
||||
# The release-cli workflow bumps `version`, `url`, and `sha256` per platform
|
||||
# via `brew bump-formula-pr`. This template is the source shape — copy it
|
||||
# into the tap repo as `Formula/claudemesh.rb` when bootstrapping, then let
|
||||
# CI keep it up to date.
|
||||
|
||||
class Claudemesh < Formula
|
||||
desc "Peer mesh for Claude Code sessions"
|
||||
homepage "https://claudemesh.com"
|
||||
version "1.0.0-alpha.28"
|
||||
license "MIT"
|
||||
|
||||
on_macos do
|
||||
if Hardware::CPU.arm?
|
||||
url "https://github.com/alezmad/claudemesh/releases/download/cli-v#{version}/claudemesh-darwin-arm64"
|
||||
sha256 "REPLACED_BY_CI"
|
||||
else
|
||||
url "https://github.com/alezmad/claudemesh/releases/download/cli-v#{version}/claudemesh-darwin-x64"
|
||||
sha256 "REPLACED_BY_CI"
|
||||
end
|
||||
end
|
||||
|
||||
on_linux do
|
||||
if Hardware::CPU.arm?
|
||||
url "https://github.com/alezmad/claudemesh/releases/download/cli-v#{version}/claudemesh-linux-arm64"
|
||||
sha256 "REPLACED_BY_CI"
|
||||
else
|
||||
url "https://github.com/alezmad/claudemesh/releases/download/cli-v#{version}/claudemesh-linux-x64"
|
||||
sha256 "REPLACED_BY_CI"
|
||||
end
|
||||
end
|
||||
|
||||
def install
|
||||
bin.install Dir["*"].first => "claudemesh"
|
||||
end
|
||||
|
||||
def caveats
|
||||
<<~EOS
|
||||
To enable click-to-launch from invite emails:
|
||||
claudemesh url-handler install
|
||||
|
||||
To show live peer count in Claude Code:
|
||||
claudemesh install --status-line
|
||||
|
||||
Shell completions:
|
||||
claudemesh completions zsh > "$(brew --prefix)/share/zsh/site-functions/_claudemesh"
|
||||
EOS
|
||||
end
|
||||
|
||||
test do
|
||||
assert_match "claudemesh", shell_output("#{bin}/claudemesh --version")
|
||||
end
|
||||
end
|
||||
31
packaging/winget/claudemesh.yaml.template
Normal file
31
packaging/winget/claudemesh.yaml.template
Normal file
@@ -0,0 +1,31 @@
|
||||
# winget manifest template for claudemesh.
|
||||
# Submit via PR to microsoft/winget-pkgs after each non-prerelease cli-v* tag.
|
||||
# The release-cli workflow can automate this with a wingetcreate step.
|
||||
|
||||
PackageIdentifier: Claudemesh.Claudemesh
|
||||
PackageVersion: 1.0.0
|
||||
PackageName: Claudemesh
|
||||
Publisher: Alejandro Gutierrez
|
||||
License: MIT
|
||||
LicenseUrl: https://github.com/alezmad/claudemesh/blob/main/LICENSE
|
||||
ShortDescription: Peer mesh for Claude Code sessions
|
||||
Description: |-
|
||||
Claudemesh connects multiple Claude Code sessions into a peer mesh.
|
||||
End-to-end encrypted, keys stay on your machine. Invite, share state,
|
||||
verify safety numbers, back up encrypted configs.
|
||||
Moniker: claudemesh
|
||||
Tags:
|
||||
- claude
|
||||
- cli
|
||||
- mesh
|
||||
- ai
|
||||
- developer-tools
|
||||
Installers:
|
||||
- Architecture: x64
|
||||
InstallerType: portable
|
||||
InstallerUrl: https://github.com/alezmad/claudemesh/releases/download/cli-v{{version}}/claudemesh-windows-x64.exe
|
||||
InstallerSha256: REPLACED_BY_CI
|
||||
Commands:
|
||||
- claudemesh
|
||||
ManifestType: singleton
|
||||
ManifestVersion: 1.6.0
|
||||
Reference in New Issue
Block a user