From 2507bdd45295ff7f7a91570bf8067c890a4dbfd1 Mon Sep 17 00:00:00 2001 From: Ercan Er Date: Sat, 19 Sep 2026 01:41:28 +0300 Subject: [PATCH 1/2] ci: yayinlanacak eklenti paketini uc sanal kosucuda gercekten calistir macOS ve Linux paketlerinin calisip calismadigini bugune kadar kimse dogrulamamisti; ikililer yalnizca derleniyordu. Bu, dogrulamayi bir gonullunun makinesine bagimli birakiyordu. Daha ciddisi CI kaydindan cikti: macOS kosucusu `macos-26-arm64` (host aarch64-apple-darwin), yayinlanan ikili ise x86_64-apple-darwin olarak capraz derleniyor ve hic calistirilmiyordu. Apple Silicon bir Mac'te bu ikili ancak Rosetta 2 ile calisir. - smoke-test-extension.py: paketi isletim sisteminin kendi unzip'iyle acar (calistirma izni ancak boyle sinanir), manifest'teki komutu calistirir, MCP el sikismasi + arac listesi + salt-okunur bir aracin gercek cagrisi. Ayrica sunucu surumu ile manifest surumunun ve ilan edilen araclar ile sunulanlarin esit oldugunu denetler. - release.yml artik her PR'da da kuru kosu yapiyor: ayni derleme, ayni paketleme, duman testi. Yukleme yalnizca v* etiketinde. Bozuk paket etiketlenmeden yakalanir. - rust.yml'deki yinelenen "Build release" adimi kaldirildi. Yerel dogrulama (Windows): saglam paket gecti; kasitli bozulmus uc paket (surum kaymasi, eksik arac bildirimi, eksik ikili) dogru mesajla ve cikis kodu 1 ile dustu. Calistirma izni dali Windows'ta sinanamaz, onu macOS/Linux kosuculari sinayacak. Co-Authored-By: Claude Opus 5 --- .github/workflows/release.yml | 14 ++++ .github/workflows/rust.yml | 2 - smoke-test-extension.py | 135 ++++++++++++++++++++++++++++++++++ 3 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 smoke-test-extension.py diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 62dee7e..45a196b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,13 @@ name: Release +# Her PR'da da calisir (kuru kosu): yayinlanacak artefakti ayni yoldan +# derleyip paketler ve gercekten calistirir. Yukleme adimi yalnizca v* +# etiketinde isler. Boylece bozuk bir paket etiketlenmeden yakalanir. on: push: tags: ["v*"] + pull_request: + branches: ["main"] permissions: contents: write @@ -64,7 +69,16 @@ jobs: - name: Package Claude Desktop extension run: python package-extension.py ${{ matrix.asset }} ${{ matrix.mcpb }} + # Yayinlanacak paketi Claude Desktop'in yapacagi gibi ac ve calistir: + # isletim sisteminin kendi unzip'i (calistirma izni ancak boyle sinanir), + # MCP el sikismasi, arac listesi, salt-okunur bir aracin gercek cagrisi. + # macOS koşucusu ARM; x86_64 ikili burada yalnizca Rosetta ile calisir, + # bu adim o yuzden "Apple Silicon Mac'te calisiyor mu"nun da cevabi. + - name: Smoke test packaged extension + run: python smoke-test-extension.py ${{ matrix.mcpb }} + - name: Upload to GitHub Release + if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 with: files: | diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8d8ef67..5662af8 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -37,5 +37,3 @@ jobs: - name: Run tests run: cargo test --verbose - - name: Build release - run: cargo build --release --verbose diff --git a/smoke-test-extension.py b/smoke-test-extension.py new file mode 100644 index 0000000..c510f35 --- /dev/null +++ b/smoke-test-extension.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Paketlenmis .mcpb eklentisini gercekten calistirip dogrular. + +Kullanim: python smoke-test-extension.py + +Claude Desktop'in bir eklentiyi kurarken yaptigini taklit eder: + +1. Paketi isletim sisteminin KENDI acicisiyla acar. macOS/Linux'ta bu + `unzip`tir; ikilinin calistirma izni ancak boyle sinanir (Python'un + zipfile modulu Unix izinlerini uygulamaz, sinanmasi gereken seyi atlar). +2. manifest.json'daki komutu ${__dirname} cozerek calistirir. +3. MCP el sikismasini yapar, araclari listeler, salt-okunur bir araci + gercekten cagirir. + +Sinirlari: Claude Desktop'in kendi aciciyi degil isletim sisteminin +`unzip`ini kullanir; macOS Gatekeeper davranisini sinamaz (CI koşucusunda +indirilen dosyaya karantina isareti konmaz). +""" +import json +import os +import pathlib +import shutil +import subprocess +import sys +import tempfile +import zipfile + +READ_ONLY_TOOL = "status" + + +def fail(message: str) -> None: + print(f"BASARISIZ: {message}") + raise SystemExit(1) + + +def extract(package: pathlib.Path, dest: pathlib.Path) -> None: + if os.name == "nt": + zipfile.ZipFile(package).extractall(dest) + return + if shutil.which("unzip") is None: + fail("`unzip` bulunamadi; calistirma izni sinanamaz, zipfile'a dusmuyoruz") + subprocess.run(["unzip", "-q", str(package), "-d", str(dest)], check=True) + + +def rpc(request_id: int, method: str, params: dict | None = None) -> str: + message = {"jsonrpc": "2.0", "id": request_id, "method": method} + if params is not None: + message["params"] = params + return json.dumps(message) + + +def main() -> int: + if len(sys.argv) != 2: + print(__doc__) + return 2 + + package = pathlib.Path(sys.argv[1]).resolve() + if not package.is_file(): + fail(f"paket bulunamadi: {package}") + + with tempfile.TemporaryDirectory() as tmp: + root = pathlib.Path(tmp) + extract(package, root) + + manifest = json.loads((root / "manifest.json").read_text(encoding="utf-8")) + config = manifest["server"]["mcp_config"] + command = pathlib.Path(config["command"].replace("${__dirname}", str(root))) + args = config.get("args", []) + + if not command.is_file(): + fail(f"manifest'in gosterdigi ikili pakette yok: {command.name}") + if os.name != "nt" and not os.access(command, os.X_OK): + fail(f"{command.name} calistirilabilir degil (mod {oct(command.stat().st_mode & 0o777)})") + + requests = "\n".join([ + rpc(1, "initialize", { + "protocolVersion": "2024-11-05", + "capabilities": {}, + "clientInfo": {"name": "smoke-test", "version": "1"}, + }), + rpc(2, "tools/list"), + rpc(3, "tools/call", {"name": READ_ONLY_TOOL, "arguments": {}}), + ]) + "\n" + + try: + proc = subprocess.run( + [str(command), *args], input=requests, capture_output=True, + text=True, encoding="utf-8", errors="replace", timeout=120, + ) + except OSError as err: + # Burasi, ikili bu mimaride hic calistirilamiyorsa dusulen yer + # (ornegin Rosetta'siz bir ARM Mac'te x86_64 ikili). + fail(f"ikili baslatilamadi: {err}") + + responses = {} + for line in proc.stdout.splitlines(): + line = line.strip() + if line.startswith("{"): + message = json.loads(line) + responses[message.get("id")] = message + + for request_id in (1, 2, 3): + if request_id not in responses: + fail(f"istek {request_id} yanitsiz kaldi. stderr:\n{proc.stderr[-2000:]}") + if "error" in responses[request_id]: + fail(f"istek {request_id} hata dondu: {responses[request_id]['error']}") + + server = responses[1]["result"]["serverInfo"] + if server["name"] != manifest["name"]: + fail(f"sunucu adi {server['name']!r}, manifest {manifest['name']!r}") + if server["version"] != manifest["version"]: + fail(f"sunucu surumu {server['version']!r}, manifest {manifest['version']!r} " + "(Cargo.toml ile manifest.json kaymis)") + + served = sorted(tool["name"] for tool in responses[2]["result"]["tools"]) + declared = sorted(tool["name"] for tool in manifest.get("tools", [])) + if served != declared: + fail(f"manifest'in ilan ettigi araclar sunulanlarla ayni degil\n" + f" ilan edilen: {declared}\n sunulan: {served}") + + call = responses[3]["result"] + text = " ".join(part.get("text", "") for part in call.get("content", [])) + if call.get("isError") or not text.strip(): + fail(f"{READ_ONLY_TOOL} araci basarisiz: {text[:500]}") + + print(f"GECTI: {package.name}") + print(f" platform : {manifest['compatibility']['platforms']}") + print(f" sunucu : {server['name']} {server['version']}") + print(f" araclar : {len(served)} -> {', '.join(served)}") + print(f" {READ_ONLY_TOOL:9}: {text.strip().splitlines()[0]}") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) From 21e814eb8fbe3c496b2405b815c9da44a477aa50 Mon Sep 17 00:00:00 2001 From: Ercan Er Date: Sat, 19 Sep 2026 06:32:48 +0300 Subject: [PATCH 2/2] feat: macOS icin universal ikili yayinla (x86_64 + arm64) Yayinlanan macOS ikilisi yalnizca x86_64'tu. Apple Silicon bir Mac'te bu ikili ancak Rosetta 2 ile calisir ve macOS Rosetta'yi varsayilan olarak kurmuyor. Bir onceki commit'in duman testi ARM kosucuda gecti, ama bu yalnizca kosucuda Rosetta oldugunu kanitliyordu; Rosetta'siz bir Mac icin hicbir sey soylemiyordu. - release.yml: macOS'ta x86_64-apple-darwin ve aarch64-apple-darwin ayri derlenip `lipo -create` ile birlestiriliyor. `lipo -archs` ile iki dilimin de varligi dogrulaniyor, eksikse is duser. Varlik adi: claude-code-setup-macos-universal (.mcpb adi degismedi). - Duman testi macOS'ta iki kez kosuyor: varsayilan kosu arm64 dilimini, SMOKE_ARCH=x86_64 kosusu Intel Mac'lerin alacagi dilimi calistiriyor. - smoke-test-extension.py: SMOKE_ARCH destegi (`arch -`); macOS disinda verilirse acik hatayla reddediliyor. - install-macos.sh: once universal, bulamazsa x86_64. Geri dusus sart: bu dal birlesip v0.1.7 cikana kadar en son surumde universal yok, yalnizca universal arayan kurucu o aralikta Mac'te kirilirdi. - INSTALLATION.md: elle indirme satirinda ikili adi sabitlenmisti; kurucuya yonlendirildi. Kanca kurulumu git deposunda yapilacak sekilde ayrildi (ev dizininde zaten atlaniyordu). - Surum 0.1.7. Yerel dogrulama: kurucu secim mantigi gercek v0.1.6 JSON'unda x86_64'e, universal iceren surumde universal'e dustu; Linux ikisinde de dogru. Windows'ta duman testi 0.1.7 ile gecti; SMOKE_ARCH macOS disinda reddedildi. lipo ve arm64/x86_64 dilim kosulari yalnizca macOS kosucusunda sinanabilir. Co-Authored-By: Claude Opus 5 --- .github/workflows/release.yml | 33 +++++++++++++++++++++++++++------ Cargo.lock | 2 +- Cargo.toml | 2 +- INSTALLATION.md | 12 +++++++++++- install-macos.sh | 15 +++++++++++---- manifest.json | 2 +- smoke-test-extension.py | 14 +++++++++++++- 7 files changed, 65 insertions(+), 15 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 45a196b..5313767 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,9 +29,12 @@ jobs: target: x86_64-pc-windows-msvc asset: claude-code-setup-windows-x86_64.exe mcpb: claude-code-setup-windows.mcpb + # Universal ikili: x86_64 + arm64 tek dosyada (lipo). Yalnizca x86_64 + # yayinlamak Apple Silicon'da Rosetta 2'yi sart kosuyordu; macOS + # Rosetta'yi varsayilan olarak kurmuyor. - os: macos-latest - target: x86_64-apple-darwin - asset: claude-code-setup-macos-x86_64 + target: x86_64-apple-darwin,aarch64-apple-darwin + asset: claude-code-setup-macos-universal mcpb: claude-code-setup-macos.mcpb runs-on: ${{ matrix.os }} @@ -53,10 +56,21 @@ jobs: python-version: "3.x" - name: Build release binary + if: runner.os != 'macOS' run: cargo build --release --target ${{ matrix.target }} - - name: Stage asset (Unix) - if: runner.os != 'Windows' + - name: Build universal macOS binary + if: runner.os == 'macOS' + run: | + cargo build --release --target x86_64-apple-darwin + cargo build --release --target aarch64-apple-darwin + lipo -create -output ${{ matrix.asset }} target/x86_64-apple-darwin/release/claude-code-setup target/aarch64-apple-darwin/release/claude-code-setup + archs=$(lipo -archs ${{ matrix.asset }}) + echo "dilimler: $archs" + [[ "$archs" == *x86_64* && "$archs" == *arm64* ]] || { echo "universal degil: $archs"; exit 1; } + + - name: Stage asset (Linux) + if: runner.os == 'Linux' run: cp target/${{ matrix.target }}/release/claude-code-setup ${{ matrix.asset }} - name: Stage asset (Windows) @@ -72,11 +86,18 @@ jobs: # Yayinlanacak paketi Claude Desktop'in yapacagi gibi ac ve calistir: # isletim sisteminin kendi unzip'i (calistirma izni ancak boyle sinanir), # MCP el sikismasi, arac listesi, salt-okunur bir aracin gercek cagrisi. - # macOS koşucusu ARM; x86_64 ikili burada yalnizca Rosetta ile calisir, - # bu adim o yuzden "Apple Silicon Mac'te calisiyor mu"nun da cevabi. + # macOS'ta bu varsayilan kosu universal ikilinin arm64 dilimini calistirir. - name: Smoke test packaged extension run: python smoke-test-extension.py ${{ matrix.mcpb }} + # Varsayilan kosu ARM koşucuda arm64 dilimini calistirir. Intel Mac'lerin + # alacagi x86_64 dilimini de ayrica calistir (koşucuda Rosetta ile). + - name: Smoke test x86_64 slice + if: runner.os == 'macOS' + env: + SMOKE_ARCH: x86_64 + run: python smoke-test-extension.py ${{ matrix.mcpb }} + - name: Upload to GitHub Release if: startsWith(github.ref, 'refs/tags/v') uses: softprops/action-gh-release@v2 diff --git a/Cargo.lock b/Cargo.lock index e849bd5..9046566 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -378,7 +378,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "claude-code-setup" -version = "0.1.6" +version = "0.1.7" dependencies = [ "anyhow", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 1601e87..fa0abce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "claude-code-setup" -version = "0.1.6" +version = "0.1.7" edition = "2021" authors = ["Ercan ER ", "Kassam "] description = "Bagimsiz Rust CLI: Claude Code ortam denetimi, dinamik MCP yonetimi, semantik + graf hafiza motoru" diff --git a/INSTALLATION.md b/INSTALLATION.md index fb3c28d..943175a 100644 --- a/INSTALLATION.md +++ b/INSTALLATION.md @@ -156,7 +156,17 @@ Invoke-WebRequest -Uri "https://github.com/Ercaner1988/claude-code-setup-rustifi 2. **Kurulum Komutunu Yapıştırın ve Çalıştırın:** ```bash -curl -LO https://github.com/Ercaner1988/claude-code-setup-rustified/releases/latest/download/claude-code-setup-macos-x86_64 && chmod +x claude-code-setup-macos-x86_64 && ./claude-code-setup-macos-x86_64 install --hooks +curl -fsSL https://raw.githubusercontent.com/Ercaner1988/claude-code-setup-rustified/main/install-macos.sh | bash +``` + +Kurucu Mac'inin Intel mi Apple Silicon mı olduğuna bakmaz, gerek de yok: +indirdiği ikili ikisinde de yerel olarak çalışır (universal), Rosetta gerekmez. + +Güvenlik kancasını bir **git deposunun kök dizininde** kur — kanca depoya +özeldir, ev dizininde çalıştırınca atlanır: + +```bash +cd ~/depom && claude-code-setup install-hooks ``` --- diff --git a/install-macos.sh b/install-macos.sh index d476775..64cf25f 100644 --- a/install-macos.sh +++ b/install-macos.sh @@ -52,9 +52,12 @@ echo -e "${GREEN} Found: $LATEST_TAG${NC}" # Pick the asset for THIS operating system. Bu betik hem macOS hem Linux icin # kullaniliyor; macOS ikilisini sabitlemek Linux'ta "cannot execute binary # file" hatasi veriyordu. +# macOS: once universal ikili (Intel + Apple Silicon, Rosetta gerektirmez), +# yoksa eski surumlerdeki x86_64 ikili. Geri dusus, universal'in henuz +# yayinlanmadigi surumlerde kurucunun kirilmamasi icin. case "$(uname -s)" in - Darwin) ASSET_MARKER="macos-x86_64"; OS_LABEL="macOS" ;; - Linux) ASSET_MARKER="linux-x86_64"; OS_LABEL="Linux" ;; + Darwin) ASSET_MARKERS="macos-universal macos-x86_64"; OS_LABEL="macOS" ;; + Linux) ASSET_MARKERS="linux-x86_64"; OS_LABEL="Linux" ;; *) echo "Unsupported operating system: $(uname -s)" echo "Supported: macOS, Linux. On Windows use install-windows.ps1." @@ -62,10 +65,14 @@ case "$(uname -s)" in ;; esac -DOWNLOAD_URL=$(echo "$API_RESPONSE" | grep -o "\"browser_download_url\": *\"[^\"]*${ASSET_MARKER}[^\"]*" | head -1 | cut -d'"' -f4) +DOWNLOAD_URL="" +for ASSET_MARKER in $ASSET_MARKERS; do + DOWNLOAD_URL=$(echo "$API_RESPONSE" | grep -o "\"browser_download_url\": *\"[^\"]*${ASSET_MARKER}[^\"]*" | head -1 | cut -d'"' -f4) + [[ -n "$DOWNLOAD_URL" ]] && break +done if [[ -z "$DOWNLOAD_URL" ]]; then - echo "${OS_LABEL} x64 binary not found in release" + echo "${OS_LABEL} binary not found in release (looked for: $ASSET_MARKERS)" exit 1 fi diff --git a/manifest.json b/manifest.json index 7dd13ae..1374af7 100644 --- a/manifest.json +++ b/manifest.json @@ -2,7 +2,7 @@ "manifest_version": "0.3", "name": "claude-code-setup", "display_name": "Claude Code Setup", - "version": "0.1.6", + "version": "0.1.7", "description": "MCP management, semantic memory engine, and security auditing for Claude Code environment setup.", "author": { "name": "Ercan ER", diff --git a/smoke-test-extension.py b/smoke-test-extension.py index c510f35..03ce296 100644 --- a/smoke-test-extension.py +++ b/smoke-test-extension.py @@ -12,6 +12,10 @@ 3. MCP el sikismasini yapar, araclari listeler, salt-okunur bir araci gercekten cagirir. +macOS'ta SMOKE_ARCH=x86_64 (veya arm64) verilirse ikili `arch -` +ile o dilimden calistirilir. Universal ikilinin iki diliminin de gercekten +calistigi ancak boyle sinanir; varsayilan kosu yalnizca yerel dilimi calistirir. + Sinirlari: Claude Desktop'in kendi aciciyi degil isletim sisteminin `unzip`ini kullanir; macOS Gatekeeper davranisini sinamaz (CI koşucusunda indirilen dosyaya karantina isareti konmaz). @@ -82,9 +86,16 @@ def main() -> int: rpc(3, "tools/call", {"name": READ_ONLY_TOOL, "arguments": {}}), ]) + "\n" + launch = [str(command), *args] + arch = os.environ.get("SMOKE_ARCH", "").strip() + if arch: + if sys.platform != "darwin": + fail("SMOKE_ARCH yalnizca macOS'ta anlamli") + launch = ["arch", f"-{arch}", *launch] + try: proc = subprocess.run( - [str(command), *args], input=requests, capture_output=True, + launch, input=requests, capture_output=True, text=True, encoding="utf-8", errors="replace", timeout=120, ) except OSError as err: @@ -125,6 +136,7 @@ def main() -> int: print(f"GECTI: {package.name}") print(f" platform : {manifest['compatibility']['platforms']}") + print(f" dilim : {arch or 'yerel'}") print(f" sunucu : {server['name']} {server['version']}") print(f" araclar : {len(served)} -> {', '.join(served)}") print(f" {READ_ONLY_TOOL:9}: {text.strip().splitlines()[0]}")