From 45bc95181bf93c665837d4021514b14eb556de8d Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Thu, 18 Jun 2026 11:11:39 +0900 Subject: [PATCH 1/4] feat: distribute csp via Homebrew tap Add a release pipeline that publishes standalone csp binaries and updates the pleaseai/homebrew-tap formula on each release. - wire `csp --version` / `-V` and sync version into the MCP server - annotate src/version.ts for release-please version bumping - add release-please config + manifest (node release-type) - add release-please workflow: build per-arch native binaries (onnxruntime/tree-sitter addons can't cross-compile via bun --target), upload to the GitHub release, then push csp.rb to homebrew-tap - document `brew install pleaseai/tap/csp` in both READMEs --- .github/workflows/release-please.yml | 232 +++++++++++++++++++++++++++ .release-please-manifest.json | 3 + README.ko.md | 6 + README.md | 8 +- release-please-config.json | 30 ++++ src/cli.ts | 6 + src/mcp/server.ts | 3 +- src/version.ts | 7 +- 8 files changed, 290 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..867b22c --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,232 @@ +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +name: release-please + +jobs: + release-please: + runs-on: ubuntu-latest + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: actions/create-github-app-token@v2 + id: app-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} + + - uses: googleapis/release-please-action@v4 + id: release + with: + token: ${{ steps.app-token.outputs.token }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json + + build-binaries: + needs: release-please + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + # csp embeds platform-specific native addons (onnxruntime-node, + # @kreuzberg/tree-sitter-language-pack). `bun build --compile` bundles + # the *host* platform's .node files, so each target MUST build on a + # native runner — cross-compiling with `--target` produces a binary + # with the wrong-arch addons ("bad CPU type in executable"). + include: + - os: macos-13 # Intel + target: darwin-x64 + - os: macos-14 # Apple Silicon + target: darwin-arm64 + - os: ubuntu-latest # x64 + target: linux-x64 + - os: ubuntu-24.04-arm # arm64 + target: linux-arm64 + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Bun + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.10 + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Build binary (native target) + run: | + bun build src/cli.ts --compile --outfile csp-${{ matrix.target }} + + - name: Smoke test binary + run: | + ./csp-${{ matrix.target }} --version + + - name: Generate checksum + run: | + shasum -a 256 csp-${{ matrix.target }} > csp-${{ matrix.target }}.sha256 + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: csp-${{ matrix.target }} + path: | + csp-${{ matrix.target }} + csp-${{ matrix.target }}.sha256 + + upload-release-assets: + needs: [release-please, build-binaries] + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Download all artifacts + uses: actions/download-artifact@v4 + with: + path: artifacts + + - name: Prepare release assets + run: | + mkdir -p release + find artifacts -type f -exec cp {} release/ \; + ls -lh release/ + + - name: Upload release artifacts + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release upload ${{ needs.release-please.outputs.tag_name }} \ + release/* \ + --clobber + + update-homebrew-formula: + needs: [release-please, upload-release-assets] + if: ${{ needs.release-please.outputs.release_created }} + runs-on: ubuntu-latest + + steps: + - uses: actions/create-github-app-token@v2 + id: app-token + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.PRIVATE_KEY }} + repositories: | + code-search + homebrew-tap + + - name: Checkout homebrew-tap repository + uses: actions/checkout@v4 + with: + repository: pleaseai/homebrew-tap + token: ${{ steps.app-token.outputs.token }} + path: homebrew-tap + + - name: Configure git + run: | + cd homebrew-tap + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + - name: Update Formula + run: | + VERSION=${{ needs.release-please.outputs.tag_name }} + VERSION_NO_V=${VERSION#v} + + # Download checksums + curl -L -o darwin-x64.sha256 \ + "https://github.com/${{ github.repository }}/releases/download/${VERSION}/csp-darwin-x64.sha256" + curl -L -o darwin-arm64.sha256 \ + "https://github.com/${{ github.repository }}/releases/download/${VERSION}/csp-darwin-arm64.sha256" + curl -L -o linux-x64.sha256 \ + "https://github.com/${{ github.repository }}/releases/download/${VERSION}/csp-linux-x64.sha256" + curl -L -o linux-arm64.sha256 \ + "https://github.com/${{ github.repository }}/releases/download/${VERSION}/csp-linux-arm64.sha256" + + # Extract checksums + DARWIN_X64_SHA256=$(cat darwin-x64.sha256 | awk '{print $1}') + DARWIN_ARM64_SHA256=$(cat darwin-arm64.sha256 | awk '{print $1}') + LINUX_X64_SHA256=$(cat linux-x64.sha256 | awk '{print $1}') + LINUX_ARM64_SHA256=$(cat linux-arm64.sha256 | awk '{print $1}') + + # Create or update Formula + cd homebrew-tap + cat > csp.rb << 'FORMULA_EOF' + class Csp < Formula + desc "Fast and accurate hybrid code search for agents" + homepage "https://github.com/pleaseai/code-search" + version "VERSION_PLACEHOLDER" + license "MIT" + + on_macos do + if Hardware::CPU.arm? + url "https://github.com/pleaseai/code-search/releases/download/vVERSION_PLACEHOLDER/csp-darwin-arm64" + sha256 "SHA256_DARWIN_ARM64_PLACEHOLDER" + else + url "https://github.com/pleaseai/code-search/releases/download/vVERSION_PLACEHOLDER/csp-darwin-x64" + sha256 "SHA256_DARWIN_X64_PLACEHOLDER" + end + end + + on_linux do + if Hardware::CPU.arm? + url "https://github.com/pleaseai/code-search/releases/download/vVERSION_PLACEHOLDER/csp-linux-arm64" + sha256 "SHA256_LINUX_ARM64_PLACEHOLDER" + else + url "https://github.com/pleaseai/code-search/releases/download/vVERSION_PLACEHOLDER/csp-linux-x64" + sha256 "SHA256_LINUX_X64_PLACEHOLDER" + end + end + + def install + if OS.mac? + if Hardware::CPU.arm? + bin.install "csp-darwin-arm64" => "csp" + else + bin.install "csp-darwin-x64" => "csp" + end + else + if Hardware::CPU.arm? + bin.install "csp-linux-arm64" => "csp" + else + bin.install "csp-linux-x64" => "csp" + end + end + end + + test do + assert_match version.to_s, shell_output("#{bin}/csp --version") + end + end + FORMULA_EOF + + # Replace placeholders + sed -i "s/VERSION_PLACEHOLDER/${VERSION_NO_V}/g" csp.rb + sed -i "s/SHA256_DARWIN_X64_PLACEHOLDER/${DARWIN_X64_SHA256}/" csp.rb + sed -i "s/SHA256_DARWIN_ARM64_PLACEHOLDER/${DARWIN_ARM64_SHA256}/" csp.rb + sed -i "s/SHA256_LINUX_X64_PLACEHOLDER/${LINUX_X64_SHA256}/" csp.rb + sed -i "s/SHA256_LINUX_ARM64_PLACEHOLDER/${LINUX_ARM64_SHA256}/" csp.rb + + # Check if there are changes + if git diff --quiet csp.rb 2>/dev/null; then + echo "No changes to Formula" + exit 0 + fi + + # Commit and push + git add csp.rb + git commit -m "chore: update csp to ${VERSION}" + git push origin main + env: + GITHUB_TOKEN: ${{ steps.app-token.outputs.token }} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..e18ee07 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.0" +} diff --git a/README.ko.md b/README.ko.md index 9762784..48aef02 100644 --- a/README.ko.md +++ b/README.ko.md @@ -71,11 +71,17 @@ codex plugin add csp@pleaseai 에이전트의 컨텍스트에 `csp` 사용법을 추가해 언제 어떻게 CLI를 호출할지 알 수 있도록 합니다. 먼저 `csp` CLI를 설치한 뒤, 아래 스니펫을 `AGENTS.md` 또는 `CLAUDE.md`에 추가하세요. ```bash +# Homebrew (macOS / Linux) — Node/Bun 없이 동작하는 독립 실행 바이너리 +brew install pleaseai/tap/csp + +# 또는 JavaScript 패키지 매니저로 설치 (PATH에 Bun 또는 Node 22+ 필요) bun add -g @pleaseai/csp # bun으로 설치 (권장) npm install -g @pleaseai/csp # 또는 npm pnpm add -g @pleaseai/csp # 또는 pnpm ``` +> Homebrew formula는 `bun build --compile`로 만든 자체 완결형 바이너리를 제공합니다(tree-sitter·임베딩 런타임 내장). 임베딩 모델은 첫 검색 시 내려받아 `~/.cache`에 캐시됩니다. +
AGENTS.md / CLAUDE.md 스니펫 diff --git a/README.md b/README.md index 981ffe7..a0b5d5d 100644 --- a/README.md +++ b/README.md @@ -71,11 +71,17 @@ It bundles the `csp` MCP server (`search`, `find_related`) plus a `csp-search` s Add `csp` usage instructions to your agent's context so it knows when and how to call the CLI. Install the `csp` CLI, then add the snippet below to your `AGENTS.md` or `CLAUDE.md`: ```bash -bun add -g @pleaseai/csp # Install with bun (recommended) +# Homebrew (macOS / Linux) — standalone binary, no Node/Bun required +brew install pleaseai/tap/csp + +# Or via a JavaScript package manager (needs Bun or Node 22+ on your PATH) +bun add -g @pleaseai/csp # Install with bun (recommended) npm install -g @pleaseai/csp # Or with npm pnpm add -g @pleaseai/csp # Or with pnpm ``` +> The Homebrew formula ships a self-contained binary built with `bun build --compile` (tree-sitter and embedding runtimes are bundled). The embedding model is downloaded on first search and cached under `~/.cache`. +
AGENTS.md / CLAUDE.md snippet diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 0000000..8225fbe --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "packages": { + ".": { + "release-type": "node", + "package-name": "@pleaseai/csp", + "include-component-in-tag": false, + "include-v-in-tag": true, + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance Improvements" }, + { "type": "revert", "section": "Reverts" }, + { "type": "docs", "section": "Documentation" }, + { "type": "style", "section": "Styles", "hidden": true }, + { "type": "chore", "section": "Miscellaneous Chores", "hidden": true }, + { "type": "refactor", "section": "Code Refactoring", "hidden": true }, + { "type": "test", "section": "Tests", "hidden": true }, + { "type": "build", "section": "Build System", "hidden": true }, + { "type": "ci", "section": "Continuous Integration", "hidden": true } + ], + "extra-files": [ + { + "type": "generic", + "path": "src/version.ts" + } + ] + } + } +} diff --git a/src/cli.ts b/src/cli.ts index 04e8b83..d7c7382 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -12,6 +12,7 @@ import { serve } from './mcp/server.ts' import { clearSavings, formatSavingsReport } from './stats.ts' import { ContentType } from './types.ts' import { formatResults, isGitUrl, resolveChunk } from './utils.ts' +import { version } from './version.ts' export enum Agent { Antigravity = 'antigravity', @@ -375,6 +376,11 @@ export async function runCli(argv: string[], options: RunOptions = {}): Promise< return 0 } + if (argv[0] === '-V' || argv[0] === '--version') { + process.stdout.write(`csp ${version}\n`) + return 0 + } + try { const { command, positional, flags } = parseArgs(argv) diff --git a/src/mcp/server.ts b/src/mcp/server.ts index a9f579f..90bc44d 100644 --- a/src/mcp/server.ts +++ b/src/mcp/server.ts @@ -7,6 +7,7 @@ import { loadOrBuildIndex } from '../indexing/cache.ts' import { CspIndex, loadModel } from '../indexing/index.ts' import { ContentType } from '../types.ts' import { formatResults, isGitUrl, resolveChunk } from '../utils.ts' +import { version } from '../version.ts' const REPO_DESCRIPTION = 'https:// or http:// git URL (e.g. https://github.com/org/repo) or local directory path to index and search. ' @@ -497,7 +498,7 @@ export async function createServer( } const underlying = new McpServer( - { name: 'csp', version: '0.0.0' }, + { name: 'csp', version }, { instructions: SERVER_INSTRUCTIONS }, ) diff --git a/src/version.ts b/src/version.ts index 88ac6c4..cb0faa2 100644 --- a/src/version.ts +++ b/src/version.ts @@ -5,6 +5,7 @@ // * `package.json#version` is the source of truth for npm publishing. // * Bun/tsdown don't read Python-style triples; reconstructing one would // just be dead code. -// A future integration PR will keep this in sync with `package.json#version` -// (e.g. via a generated file or a build-time replacement). -export const version = '0.0.0' +// Kept in sync with `package.json#version` by release-please via the +// `x-release-please-version` annotation below (see release-please-config.json +// `extra-files`). +export const version = '0.0.0' // x-release-please-version From 91539aac4a5f38914b86721f49604cf8c2368837 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Thu, 18 Jun 2026 11:17:02 +0900 Subject: [PATCH 2/4] ci: sync bun.lock into release PR --- .github/workflows/release-please.yml | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 867b22c..a67599d 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -29,6 +29,38 @@ jobs: config-file: release-please-config.json manifest-file: .release-please-manifest.json + # release-please bumps package.json but does not know about bun.lock. + # A version-only bump usually leaves bun.lock unchanged, but a release PR + # can also carry dependency updates — re-resolve and commit the lockfile + # into the release PR so the tagged commit always installs cleanly with + # `bun install --frozen-lockfile`. Only commits when bun.lock changes. + - name: Checkout release PR branch + if: ${{ steps.release.outputs.pr }} + uses: actions/checkout@v4 + with: + token: ${{ steps.app-token.outputs.token }} + ref: ${{ fromJson(steps.release.outputs.pr).headBranchName }} + + - name: Setup Bun + if: ${{ steps.release.outputs.pr }} + uses: oven-sh/setup-bun@v2 + with: + bun-version: 1.3.10 + + - name: Sync bun.lock + if: ${{ steps.release.outputs.pr }} + run: | + bun install --lockfile-only + if git diff --quiet bun.lock; then + echo "bun.lock already in sync" + else + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add bun.lock + git commit -m "chore: sync bun.lock" + git push origin HEAD:${{ fromJson(steps.release.outputs.pr).headBranchName }} + fi + build-binaries: needs: release-please if: ${{ needs.release-please.outputs.release_created }} From 8b814f2e4a3c3066b764ae344cca074ee4282a22 Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Thu, 18 Jun 2026 11:21:13 +0900 Subject: [PATCH 3/4] docs: correct cache path in Homebrew note (index cache is ~/.csp) --- README.ko.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.ko.md b/README.ko.md index 48aef02..0d831ee 100644 --- a/README.ko.md +++ b/README.ko.md @@ -80,7 +80,7 @@ npm install -g @pleaseai/csp # 또는 npm pnpm add -g @pleaseai/csp # 또는 pnpm ``` -> Homebrew formula는 `bun build --compile`로 만든 자체 완결형 바이너리를 제공합니다(tree-sitter·임베딩 런타임 내장). 임베딩 모델은 첫 검색 시 내려받아 `~/.cache`에 캐시됩니다. +> Homebrew formula는 `bun build --compile`로 만든 자체 완결형 바이너리를 제공합니다(tree-sitter·임베딩 런타임 내장). 인덱스는 `~/.csp/`에 캐시됩니다([ADR 0002](.please/docs/decisions/0002-index-storage-cache-model.md) 참고).
AGENTS.md / CLAUDE.md 스니펫 diff --git a/README.md b/README.md index a0b5d5d..c85a0fa 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ npm install -g @pleaseai/csp # Or with npm pnpm add -g @pleaseai/csp # Or with pnpm ``` -> The Homebrew formula ships a self-contained binary built with `bun build --compile` (tree-sitter and embedding runtimes are bundled). The embedding model is downloaded on first search and cached under `~/.cache`. +> The Homebrew formula ships a self-contained binary built with `bun build --compile` (tree-sitter and embedding runtimes are bundled). Indexes are cached under `~/.csp/` (see [ADR 0002](.please/docs/decisions/0002-index-storage-cache-model.md)).
AGENTS.md / CLAUDE.md snippet From 9b8511559d9903b91b8949de353929f980505e9b Mon Sep 17 00:00:00 2001 From: Minsu Lee Date: Thu, 18 Jun 2026 11:47:06 +0900 Subject: [PATCH 4/4] ci: pin GitHub Actions to full commit SHAs --- .github/workflows/release-please.yml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index a67599d..dfecbb2 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -16,13 +16,13 @@ jobs: release_created: ${{ steps.release.outputs.release_created }} tag_name: ${{ steps.release.outputs.tag_name }} steps: - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2 id: app-token with: app-id: ${{ secrets.APP_ID }} private-key: ${{ secrets.PRIVATE_KEY }} - - uses: googleapis/release-please-action@v4 + - uses: googleapis/release-please-action@5c625bfb5d1ff62eadeeb3772007f7f66fdcf071 # v4.4.1 id: release with: token: ${{ steps.app-token.outputs.token }} @@ -36,14 +36,14 @@ jobs: # `bun install --frozen-lockfile`. Only commits when bun.lock changes. - name: Checkout release PR branch if: ${{ steps.release.outputs.pr }} - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: token: ${{ steps.app-token.outputs.token }} ref: ${{ fromJson(steps.release.outputs.pr).headBranchName }} - name: Setup Bun if: ${{ steps.release.outputs.pr }} - uses: oven-sh/setup-bun@v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: 1.3.10 @@ -85,10 +85,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Setup Bun - uses: oven-sh/setup-bun@v2 + uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0 with: bun-version: 1.3.10 @@ -108,7 +108,7 @@ jobs: shasum -a 256 csp-${{ matrix.target }} > csp-${{ matrix.target }}.sha256 - name: Upload artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: csp-${{ matrix.target }} path: | @@ -122,10 +122,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 - name: Download all artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: path: artifacts @@ -149,7 +149,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/create-github-app-token@v2 + - uses: actions/create-github-app-token@fee1f7d63c2ff003460e3d139729b119787bc349 # v2.2.2 id: app-token with: app-id: ${{ secrets.APP_ID }} @@ -159,7 +159,7 @@ jobs: homebrew-tap - name: Checkout homebrew-tap repository - uses: actions/checkout@v4 + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 with: repository: pleaseai/homebrew-tap token: ${{ steps.app-token.outputs.token }}