@setoelkahfi / sigit / commits / cd75bec

Add multi-platform release workflows for GitHub, NPM, and PyPI

- Replace single release.yml with platform-specific workflows: - release-github.yml for GitHub Releases and Homebrew tap updates - release-npm.yml for publishing binaries and base package to NPM - release-pypi.yml for building and publishing wheels/sdist to PyPI - Add Homebrew tap update automation - Add NPM packaging scripts, templates, and TypeScript base package - Add PyPI packaging with maturin and metadata - Add .nvmrc and rust-toolchain.toml for consistent builds - Update Cargo.toml with repository, homepage, keywords, categories, and authors

Seto Elkahfi committed Apr 24, 2026 at 00:57 UTC cd75bec906625ae4cac5c441a107846bfb360952
20 files changed +1221 -171
.github/workflows/release-github.yml
+187
new file mode 100644 index 0000000..74e2fb7 --- /dev/null +++ b/.github/workflows/release-github.yml @@ -0,0 +1,187 @@ +name: GitHub Release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + tag: + description: "Release tag (e.g. v0.1.1)" + required: true + +permissions: + contents: write + actions: write + +env: + PROJECT_NAME: sigit + CARGO_TERM_COLOR: always + +jobs: + build: + name: Build binary (${{ matrix.name }}) + runs-on: ${{ matrix.runner }} + + strategy: + fail-fast: false + matrix: + include: + - name: linux-amd64 + runner: ubuntu-latest + target: x86_64-unknown-linux-gnu + - name: linux-arm64 + runner: ubuntu-24.04-arm + target: aarch64-unknown-linux-gnu + - name: win-amd64 + runner: windows-latest + target: x86_64-pc-windows-msvc + - name: win-arm64 + runner: windows-latest + target: aarch64-pc-windows-msvc + - name: macos-amd64 + runner: macos-15-intel + target: x86_64-apple-darwin + - name: macos-arm64 + runner: macos-latest + target: aarch64-apple-darwin + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Set the release version + shell: bash + run: | + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + release_version="${GITHUB_REF_NAME#v}" + else + release_version="${{ github.event.inputs.tag }}" + release_version="${release_version#v}" + fi + echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV" + + - name: Read Rust toolchain + shell: bash + run: | + rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)" + if [ -z "$rust_toolchain" ]; then + echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2 + exit 1 + fi + echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV" + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + targets: ${{ matrix.target }} + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: github-${{ matrix.target }} + + - name: Build binary + shell: bash + run: cargo build --locked --release --target ${{ matrix.target }} + + - name: Package binary + shell: bash + run: | + BIN_SUFFIX="" + if [[ "${{ matrix.runner }}" == "windows-latest" ]]; then + BIN_SUFFIX=".exe" + fi + + BIN_OUTPUT="target/${{ matrix.target }}/release/${PROJECT_NAME}${BIN_SUFFIX}" + BIN_RELEASE="${PROJECT_NAME}-${{ matrix.name }}${BIN_SUFFIX}" + + mkdir -p ./release + mv "${BIN_OUTPUT}" "./release/${BIN_RELEASE}" + + - name: Package macOS tarball for Homebrew + if: contains(matrix.target, 'apple-darwin') + shell: bash + run: | + ARCHIVE_NAME="${PROJECT_NAME}-${{ matrix.name }}.tar.gz" + + mkdir -p staging-brew + cp "./release/${PROJECT_NAME}-${{ matrix.name }}" "staging-brew/${PROJECT_NAME}" + strip "staging-brew/${PROJECT_NAME}" + + tar -C staging-brew -czf "${ARCHIVE_NAME}" "${PROJECT_NAME}" + + shasum -a 256 "${ARCHIVE_NAME}" | awk '{print $1}' > "${ARCHIVE_NAME}.sha256" + + echo "Archive: ${ARCHIVE_NAME}" + echo "SHA256: $(cat "${ARCHIVE_NAME}.sha256")" + + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: binary-${{ matrix.name }} + path: release/* + + - name: Upload macOS Homebrew artifacts + if: contains(matrix.target, 'apple-darwin') + uses: actions/upload-artifact@v4 + with: + name: homebrew-${{ matrix.name }} + path: | + ${{ env.PROJECT_NAME }}-${{ matrix.name }}.tar.gz + ${{ env.PROJECT_NAME }}-${{ matrix.name }}.tar.gz.sha256 + + release: + name: Create GitHub Release + needs: build + runs-on: ubuntu-latest + if: github.ref_type == 'tag' || github.event_name == 'workflow_dispatch' + + steps: + - name: Resolve tag + id: tag + shell: bash + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "tag=${{ github.event.inputs.tag }}" >> "$GITHUB_OUTPUT" + else + echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT" + fi + + - name: Download binary artifacts + uses: actions/download-artifact@v4 + with: + pattern: "binary-*" + path: release + merge-multiple: true + + - name: Download Homebrew artifacts + uses: actions/download-artifact@v4 + with: + pattern: "homebrew-*" + path: release + merge-multiple: true + + - name: Release + uses: softprops/action-gh-release@v2 + with: + tag_name: ${{ steps.tag.outputs.tag }} + files: release/* + + - name: Trigger Homebrew release + uses: actions/github-script@v7 + with: + script: | + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'release-homebrew.yml', + ref: 'main', + inputs: { + tag: '${{ steps.tag.outputs.tag }}' + } + }) + console.log('Dispatched release-homebrew.yml for tag ${{ steps.tag.outputs.tag }}')
.github/workflows/release-homebrew.yml
+116
new file mode 100644 index 0000000..4f08d8d --- /dev/null +++ b/.github/workflows/release-homebrew.yml @@ -0,0 +1,116 @@ +name: Homebrew CLI Release + +on: + workflow_dispatch: + inputs: + tag: + description: "Release tag (e.g. v0.1.1)" + required: true + +permissions: + contents: read + +env: + REPO: getsigit/sigit + +jobs: + update-homebrew-tap: + name: Update Homebrew tap + runs-on: ubuntu-latest + + steps: + - name: Resolve tag and version + id: release + shell: bash + run: | + TAG="${{ github.event.inputs.tag }}" + VERSION="${TAG#v}" + + echo "tag=${TAG}" >> "$GITHUB_OUTPUT" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Read SHA256 checksums from release + id: sha + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + mkdir -p artifacts + + gh release download "${{ steps.release.outputs.tag }}" \ + --repo "${{ env.REPO }}" \ + --pattern "sigit-macos-*.tar.gz.sha256" \ + --dir artifacts/ + + ARM64_SHA=$(cat artifacts/sigit-macos-arm64.tar.gz.sha256) + AMD64_SHA=$(cat artifacts/sigit-macos-amd64.tar.gz.sha256) + + echo "arm64=${ARM64_SHA}" >> "$GITHUB_OUTPUT" + echo "amd64=${AMD64_SHA}" >> "$GITHUB_OUTPUT" + + echo "ARM64 SHA256: ${ARM64_SHA}" + echo "AMD64 SHA256: ${AMD64_SHA}" + + - name: Checkout Homebrew tap + uses: actions/checkout@v6 + with: + repository: getsigit/sigit-homebrew-tap + token: ${{ secrets.HOMEBREW_TAP_TOKEN }} + path: homebrew-tap + + - name: Generate formula + shell: bash + run: | + VERSION="${{ steps.release.outputs.version }}" + TAG="${{ steps.release.outputs.tag }}" + ARM64_SHA="${{ steps.sha.outputs.arm64 }}" + AMD64_SHA="${{ steps.sha.outputs.amd64 }}" + + mkdir -p homebrew-tap/Formula + + cat > homebrew-tap/Formula/sigit.rb <<FORMULA + # frozen_string_literal: true + + # Homebrew formula for siGit Code (\`sigit\` binary). + class Sigit < Formula + desc 'AI coding agent powered by local LLM via Onde Inference' + homepage 'https://github.com/getsigit/sigit' + version '${VERSION}' + license 'Apache-2.0' + + on_macos do + on_arm do + url 'https://github.com/${REPO}/releases/download/${TAG}/sigit-macos-arm64.tar.gz' + sha256 '${ARM64_SHA}' + end + on_intel do + url 'https://github.com/${REPO}/releases/download/${TAG}/sigit-macos-amd64.tar.gz' + sha256 '${AMD64_SHA}' + end + end + + def install + bin.install 'sigit' + end + + test do + assert_match version.to_s, shell_output("#{bin}/sigit --version", 1) + end + end + FORMULA + + echo "Generated formula:" + cat homebrew-tap/Formula/sigit.rb + + - name: Commit and push + shell: bash + run: | + VERSION="${{ steps.release.outputs.version }}" + + cd homebrew-tap + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Formula/sigit.rb + git diff --cached --quiet && echo "No changes to commit" && exit 0 + git commit -m "Update sigit to ${VERSION}" + git push
.github/workflows/release-npm.yml
+220
new file mode 100644 index 0000000..cacc5fa --- /dev/null +++ b/.github/workflows/release-npm.yml @@ -0,0 +1,220 @@ +name: NPM Release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + tag: + description: "Release tag (e.g. v0.1.1)" + required: true + +permissions: + id-token: write + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + publish-npm-binaries: + name: Publish NPM platform packages + runs-on: ${{ matrix.build.OS }} + strategy: + fail-fast: false + matrix: + build: + - { + NAME: linux-x64-glibc, + OS: ubuntu-latest, + TARGET: x86_64-unknown-linux-gnu, + } + - { + NAME: linux-arm64-glibc, + OS: ubuntu-24.04-arm, + TARGET: aarch64-unknown-linux-gnu, + } + - { + NAME: win32-x64-msvc, + OS: windows-2022, + TARGET: x86_64-pc-windows-msvc, + } + - { + NAME: win32-arm64-msvc, + OS: windows-2022, + TARGET: aarch64-pc-windows-msvc, + } + - { + NAME: darwin-x64, + OS: macos-15-intel, + TARGET: x86_64-apple-darwin, + } + - { + NAME: darwin-arm64, + OS: macos-latest, + TARGET: aarch64-apple-darwin, + } + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Set the release version + shell: bash + run: | + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + release_version="${GITHUB_REF_NAME#v}" + else + release_version="${{ github.event.inputs.tag }}" + release_version="${release_version#v}" + fi + echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV" + + - name: Read Rust toolchain + shell: bash + run: | + rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)" + if [ -z "$rust_toolchain" ]; then + echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2 + exit 1 + fi + echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV" + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + + - name: Install Rust target + shell: bash + run: | + rustup target add ${{ matrix.build.TARGET }} --toolchain ${{ env.RUST_TOOLCHAIN }} + rustup target list --installed + + - name: Setup Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: npm-${{ matrix.build.TARGET }} + + - name: Build binary + shell: bash + run: cargo build --locked --release --target ${{ matrix.build.TARGET }} + + - name: Install Node + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + registry-url: "https://registry.npmjs.org" + + - name: Publish platform package to NPM + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + shell: bash + run: | + cd npm + + # Binary name from Cargo.toml + cargo_bin="sigit" + + # Prefix used for optional dependency package names + node_pkg_prefix="sigit" + + # Derive OS and architecture from the build matrix name + # Matrix name format: <os>-<arch>[-<extra>] + node_os=$(echo "${{ matrix.build.NAME }}" | cut -d '-' -f1) + export node_os + node_arch=$(echo "${{ matrix.build.NAME }}" | cut -d '-' -f2) + export node_arch + + # Set the version + export node_version="${{ env.RELEASE_VERSION }}" + + # Build the platform package name; normalise win32 → windows + if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then + export node_pkg="${node_pkg_prefix}-windows-${node_arch}" + else + export node_pkg="${node_pkg_prefix}-${node_os}-${node_arch}" + fi + + # Create the package directory + mkdir -p "${node_pkg}/bin" + + # Generate package.json + README for this platform slice + node ./scripts/render-platform-package.cjs \ + "${node_pkg}" \ + "${node_version}" \ + "${node_os}" \ + "${node_arch}" + + # Windows binaries carry a .exe extension + if [ "${{ matrix.build.OS }}" = "windows-2022" ]; then + cargo_bin="${cargo_bin}.exe" + fi + + cp "../target/${{ matrix.build.TARGET }}/release/${cargo_bin}" "${node_pkg}/bin" + + cd "${node_pkg}" + + npm_package_name="@smbcloud/${node_pkg}" + if npm view "${npm_package_name}@${node_version}" version >/dev/null 2>&1; then + echo "${npm_package_name}@${node_version} already exists on npm, skipping publish" + exit 0 + fi + + npm publish --access public --provenance + + publish-npm-base: + name: Publish base NPM package (@smbcloud/sigit) + needs: publish-npm-binaries + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Set the release version + shell: bash + run: | + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + release_version="${GITHUB_REF_NAME#v}" + else + release_version="${{ github.event.inputs.tag }}" + release_version="${release_version#v}" + fi + echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV" + + - name: Install Node + uses: actions/setup-node@v6 + with: + node-version-file: .nvmrc + registry-url: "https://registry.npmjs.org" + + - name: Publish base package to NPM + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + shell: bash + run: | + cd npm/sigit + + # Rewrite package.json with the correct release version and + # matching optionalDependency versions + node ../scripts/render-main-package.cjs \ + "./package.json" \ + "${{ env.RELEASE_VERSION }}" + + npm_package_name="@smbcloud/sigit" + npm_package_version="${{ env.RELEASE_VERSION }}" + + if npm view "${npm_package_name}@${npm_package_version}" version >/dev/null 2>&1; then + echo "${npm_package_name}@${npm_package_version} already exists on npm, skipping publish" + exit 0 + fi + + npm install + npm run build + npm publish --access public --provenance
.github/workflows/release-pypi.yml
+191
new file mode 100644 index 0000000..b249326 --- /dev/null +++ b/.github/workflows/release-pypi.yml @@ -0,0 +1,191 @@ +name: PyPI Release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + tag: + description: "Release tag (e.g. v0.1.1)" + required: true + +permissions: + id-token: write + contents: read + +env: + CARGO_TERM_COLOR: always + +jobs: + build-wheels: + name: Build PyPI wheels + runs-on: ${{ matrix.build.OS }} + strategy: + fail-fast: false + matrix: + build: + - { + NAME: linux-x64-glibc, + OS: ubuntu-latest, + TARGET: x86_64-unknown-linux-gnu, + MANYLINUX: "2014", + } + - { + NAME: linux-arm64-glibc, + OS: ubuntu-24.04-arm, + TARGET: aarch64-unknown-linux-gnu, + MANYLINUX: "2014", + } + - { + NAME: win32-x64-msvc, + OS: windows-2022, + TARGET: x86_64-pc-windows-msvc, + } + - { + NAME: win32-arm64-msvc, + OS: windows-2022, + TARGET: aarch64-pc-windows-msvc, + } + - { NAME: darwin-x64, OS: macos-latest, TARGET: x86_64-apple-darwin } + - { + NAME: darwin-arm64, + OS: macos-latest, + TARGET: aarch64-apple-darwin, + } + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Set the release version + shell: bash + run: | + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + release_version="${GITHUB_REF_NAME#v}" + else + release_version="${{ github.event.inputs.tag }}" + release_version="${release_version#v}" + fi + echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV" + + - name: Read Rust toolchain + shell: bash + run: | + rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)" + if [ -z "$rust_toolchain" ]; then + echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2 + exit 1 + fi + echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV" + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + + - name: Install Rust target + shell: bash + run: | + rustup target add ${{ matrix.build.TARGET }} --toolchain ${{ env.RUST_TOOLCHAIN }} + rustup target list --installed + + - name: Build wheel + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.build.TARGET }} + manylinux: ${{ matrix.build.MANYLINUX || 'off' }} + working-directory: pypi + args: --release --locked --compatibility pypi --out dist + before-script-linux: yum install -y perl-core + + - name: Upload wheel artifact + uses: actions/upload-artifact@v4 + with: + name: wheels-${{ matrix.build.NAME }} + path: pypi/dist/* + + build-sdist: + name: Build source distribution + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Read Rust toolchain + shell: bash + run: | + rust_toolchain="$(sed -n 's/^channel = "\(.*\)"/\1/p' rust-toolchain.toml | head -n 1)" + if [ -z "$rust_toolchain" ]; then + echo "Failed to read Rust toolchain from rust-toolchain.toml" >&2 + exit 1 + fi + echo "RUST_TOOLCHAIN=${rust_toolchain}" >> "$GITHUB_ENV" + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 + with: + toolchain: ${{ env.RUST_TOOLCHAIN }} + + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + working-directory: pypi + args: --out dist + + - name: Upload sdist artifact + uses: actions/upload-artifact@v4 + with: + name: sdist + path: pypi/dist/* + + publish-pypi: + name: Publish to PyPI + runs-on: ubuntu-latest + needs: + - build-wheels + - build-sdist + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + ref: ${{ github.event.inputs.tag || github.ref }} + + - name: Set the release version + shell: bash + run: | + if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then + release_version="${GITHUB_REF_NAME#v}" + else + release_version="${{ github.event.inputs.tag }}" + release_version="${release_version#v}" + fi + echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV" + + - name: Download distribution artifacts + uses: actions/download-artifact@v4 + with: + pattern: "*" + path: dist + merge-multiple: true + + - name: Check whether release already exists on PyPI + id: pypi-check + shell: bash + run: | + if curl -fsS "https://pypi.org/pypi/sigit-code/${RELEASE_VERSION}/json" >/dev/null 2>&1; then + echo "sigit-code ${RELEASE_VERSION} already exists on PyPI, skipping publish" + echo "exists=true" >> "$GITHUB_OUTPUT" + else + echo "exists=false" >> "$GITHUB_OUTPUT" + fi + + - name: Publish distribution + if: steps.pypi-check.outputs.exists != 'true' + uses: pypa/gh-action-pypi-publish@release/v1 + with: + skip-existing: true
.github/workflows/release.yml
-171
deleted file mode 100644 index fff0108..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,171 +0,0 @@ -name: Release - -on: - push: - tags: - - "v*.*.*" - workflow_dispatch: - inputs: - tag: - description: "Release tag (e.g. v0.1.0)" - required: true - -permissions: - contents: write - -env: - CARGO_TERM_COLOR: always - -jobs: - build: - name: Build ${{ matrix.artifact }} - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - include: - - target: aarch64-apple-darwin - os: macos-26 - artifact: sigit-darwin-aarch64 - binary: sigit - archive_ext: tar.gz - - - target: x86_64-apple-darwin - os: macos-26-intel - artifact: sigit-darwin-x86_64 - binary: sigit - archive_ext: tar.gz - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ github.event.inputs.tag || github.ref }} - - - name: Set release version - shell: bash - run: | - if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then - release_version="${GITHUB_REF_NAME#v}" - else - release_version="${{ github.event.inputs.tag }}" - release_version="${release_version#v}" - fi - - echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV" - - - name: Verify Cargo.toml version matches tag - shell: bash - run: | - CARGO_VERSION=$(cargo metadata --no-deps --format-version 1 \ - | jq -r '.packages[] | select(.name == "sigit") | .version') - - echo "Cargo.toml version : ${CARGO_VERSION}" - echo "Release tag version: ${RELEASE_VERSION}" - - if [[ "${CARGO_VERSION}" != "${RELEASE_VERSION}" ]]; then - echo "::error::Version mismatch — bump Cargo.toml version before tagging." - exit 1 - fi - - - name: Install Rust toolchain - uses: dtolnay/rust-toolchain@stable - with: - targets: ${{ matrix.target }} - - - name: Setup Rust cache - uses: Swatinem/rust-cache@v2 - with: - key: release-${{ matrix.target }} - - - name: Build - shell: bash - run: cargo build --release --locked --target ${{ matrix.target }} - - - name: Package (tar.gz) - if: matrix.archive_ext == 'tar.gz' - shell: bash - run: | - binary="target/${{ matrix.target }}/release/${{ matrix.binary }}" - archive="${{ matrix.artifact }}.tar.gz" - - tar -czf "${archive}" -C "$(dirname "${binary}")" "${{ matrix.binary }}" - - echo "ARCHIVE=${archive}" >> "$GITHUB_ENV" - - - name: Package (zip) - if: matrix.archive_ext == 'zip' - shell: pwsh - run: | - $binary = "target\${{ matrix.target }}\release\${{ matrix.binary }}" - $archive = "${{ matrix.artifact }}.zip" - - Compress-Archive -Path $binary -DestinationPath $archive - - "ARCHIVE=$archive" | Out-File -FilePath $env:GITHUB_ENV -Append - - - name: Upload archive - uses: actions/upload-artifact@v4 - with: - name: ${{ matrix.artifact }} - path: ${{ env.ARCHIVE }} - if-no-files-found: error - retention-days: 1 - - release: - name: Publish GitHub Release - needs: build - runs-on: ubuntu-latest - permissions: - contents: write - - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ github.event.inputs.tag || github.ref }} - - - name: Set release version and tag - shell: bash - run: | - if [[ "${GITHUB_REF_TYPE}" == "tag" ]]; then - release_tag="${GITHUB_REF_NAME}" - release_version="${GITHUB_REF_NAME#v}" - else - release_tag="${{ github.event.inputs.tag }}" - release_version="${{ github.event.inputs.tag }}" - release_version="${release_version#v}" - fi - - echo "RELEASE_TAG=${release_tag}" >> "$GITHUB_ENV" - echo "RELEASE_VERSION=${release_version}" >> "$GITHUB_ENV" - - - name: Download all archives - uses: actions/download-artifact@v4 - with: - path: archives - merge-multiple: true - - - name: Check whether release already exists - id: release-check - shell: bash - run: | - if gh release view "${RELEASE_TAG}" --repo "${{ github.repository }}" >/dev/null 2>&1; then - echo "Release ${RELEASE_TAG} already exists — skipping." - echo "exists=true" >> "$GITHUB_OUTPUT" - else - echo "exists=false" >> "$GITHUB_OUTPUT" - fi - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Create release and upload archives - if: steps.release-check.outputs.exists != 'true' - shell: bash - run: | - gh release create "${RELEASE_TAG}" \ - --title "siGit ${RELEASE_TAG}" \ - --generate-notes \ - archives/* - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.nvmrc
+1
new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20
Cargo.toml
+6
index eaff4f3..6204c4d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,12 @@ edition = "2024" description = "siGit Code — ACP-compatible AI coding agent for smbCloud platform." documentation = "https://github.com/getsigit/sigit" license = "Apache-2.0" +repository = "https://github.com/getsigit/sigit" +homepage = "https://smbcloud.xyz" +readme = "README.md" +keywords = ["sigit", "cli", "ai", "coding-agent", "llm"] +categories = ["command-line-utilities"] +authors = ["Seto Elkahfi <hej@setoelkahfi.se>"] [[bin]] name = "sigit"
npm/README.md.tmpl
+22
new file mode 100644 index 0000000..3fdece4 --- /dev/null +++ b/npm/README.md.tmpl @@ -0,0 +1,22 @@ +<h1 align="center">siGit Code</h1> + +## `${node_pkg}` + +This is a platform-specific binary for [`@smbcloud/sigit`](https://www.npmjs.com/package/@smbcloud/sigit). You don't need to install it directly. + +Install the main package instead: + +```sh +npm install -g @smbcloud/sigit +``` + +npm pulls in the right binary for your OS and architecture. + +## Links + +- [Source code](https://github.com/getsigit/sigit) +- [Issues](https://github.com/getsigit/sigit/issues) + +## License + +[Apache-2.0](https://github.com/getsigit/sigit/blob/main/LICENSE)
npm/package-main.json.tmpl
+41
new file mode 100644 index 0000000..3bd2a63 --- /dev/null +++ b/npm/package-main.json.tmpl @@ -0,0 +1,41 @@ +{ + "name": "@smbcloud/sigit", + "version": "${release_version}", + "keywords": [ + "sigit", + "cli", + "ai", + "coding-agent", + "llm", + "on-device", + "smbcloud", + "acp" + ], + "bin": { + "sigit": "lib/index.js" + }, + "files": ["lib", "README.md"], + "description": "AI coding agent powered by local LLM via Onde Inference.", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/getsigit/sigit.git" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "build": "tsc", + "dev": "npm run build && node lib/index.js" + }, + "devDependencies": { + "@types/node": "^18.15.11", + "typescript": "^5.0.0" + }, + "optionalDependencies": { + "@smbcloud/sigit-darwin-arm64": "${release_version}", + "@smbcloud/sigit-darwin-x64": "${release_version}", + "@smbcloud/sigit-linux-arm64": "${release_version}", + "@smbcloud/sigit-linux-x64": "${release_version}", + "@smbcloud/sigit-windows-arm64": "${release_version}", + "@smbcloud/sigit-windows-x64": "${release_version}" + } +}
npm/package.json.tmpl
+14
new file mode 100644 index 0000000..ac0d86a --- /dev/null +++ b/npm/package.json.tmpl @@ -0,0 +1,14 @@ +{ + "name": "@smbcloud/${node_pkg}", + "version": "${node_version}", + "description": "Platform binary for siGit Code.", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/getsigit/sigit.git" + }, + "keywords": ["${node_pkg}","cli","binary","sigit","ai","coding-agent"], + "os": ["${node_os}"], + "cpu": ["${node_arch}"], + "files": ["bin"] +}
npm/scripts/render-main-package.cjs
+34
new file mode 100644 index 0000000..66adc97 --- /dev/null +++ b/npm/scripts/render-main-package.cjs @@ -0,0 +1,34 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const path = require("path"); + +const [outputPath, version] = process.argv.slice(2); + +if (!outputPath || !version) { + throw new Error("Usage: render-main-package.cjs <output-path> <version>"); +} + +const npmRoot = path.resolve(__dirname, ".."); + +const vars = { + release_version: version, +}; + +/** + * Replace every `${key}` in the template with the corresponding value from vars. + */ +function interpolate(template, variables) { + return template.replace(/\$\{(\w+)\}/g, (match, key) => { + if (key in variables) return variables[key]; + return match; + }); +} + +// Read and interpolate package-main.json.tmpl +const template = fs.readFileSync( + path.join(npmRoot, "package-main.json.tmpl"), + "utf-8", +); + +fs.writeFileSync(path.resolve(outputPath), interpolate(template, vars));
npm/scripts/render-platform-package.cjs
+56
new file mode 100644 index 0000000..564ea34 --- /dev/null +++ b/npm/scripts/render-platform-package.cjs @@ -0,0 +1,56 @@ +#!/usr/bin/env node + +const fs = require("fs"); +const path = require("path"); + +const [packageName, version, operatingSystem, architecture] = + process.argv.slice(2); + +if (!packageName || !version || !operatingSystem || !architecture) { + throw new Error( + "Usage: render-platform-package.cjs <package-name> <version> <os> <arch>", + ); +} + +const npmRoot = path.resolve(__dirname, ".."); +const packageDirectory = path.resolve(npmRoot, packageName); + +fs.mkdirSync(packageDirectory, { recursive: true }); + +// Variable map for template interpolation +const vars = { + node_pkg: packageName, + node_version: version, + node_os: operatingSystem, + node_arch: architecture, +}; + +/** + * Replace every `${key}` in the template with the corresponding value from vars. + */ +function interpolate(template, variables) { + return template.replace(/\$\{(\w+)\}/g, (match, key) => { + if (key in variables) return variables[key]; + return match; + }); +} + +// Read and interpolate package.json.tmpl +const packageTemplate = fs.readFileSync( + path.join(npmRoot, "package.json.tmpl"), + "utf-8", +); +fs.writeFileSync( + path.join(packageDirectory, "package.json"), + interpolate(packageTemplate, vars), +); + +// Read and interpolate README.md.tmpl +const readmeTemplate = fs.readFileSync( + path.join(npmRoot, "README.md.tmpl"), + "utf-8", +); +fs.writeFileSync( + path.join(packageDirectory, "README.md"), + interpolate(readmeTemplate, vars), +);
npm/sigit/.gitignore
+2
new file mode 100644 index 0000000..3b4e8dc --- /dev/null +++ b/npm/sigit/.gitignore @@ -0,0 +1,2 @@ +lib/ +node_modules/
npm/sigit/README.md
+85
new file mode 100644 index 0000000..7050b47 --- /dev/null +++ b/npm/sigit/README.md @@ -0,0 +1,85 @@ +<h1 align="center">siGit Code</h1> + +<p align="center"> + AI coding agent powered by local LLM via Onde Inference. +</p> + +<p align="center"> + <a href="https://www.npmjs.com/package/@smbcloud/sigit"><img src="https://img.shields.io/npm/v/@smbcloud/sigit?style=flat-square&labelColor=17211D&color=235843" alt="npm"></a> + <a href="https://crates.io/crates/sigit"><img src="https://img.shields.io/crates/v/sigit?style=flat-square&labelColor=17211D&color=235843" alt="crates.io"></a> + <a href="https://pypi.org/project/sigit/"><img src="https://img.shields.io/pypi/v/sigit?style=flat-square&labelColor=17211D&color=235843" alt="PyPI"></a> +</p> + +--- + +## Install + +```sh +npm install -g @smbcloud/sigit +``` + +The right binary for your platform gets pulled in automatically. Works on macOS (Apple Silicon and Intel), Linux (x64 and arm64), and Windows (x64 and arm64). + +### Other ways to install + +| Method | Command | +|---|---| +| **Homebrew** | `brew install getsigit/homebrew-tap/sigit` | +| **pip** | `pip install sigit` | +| **Cargo** | `cargo install sigit` | + +--- + +## Usage + +```sh +sigit +``` + +Opens a TUI coding agent that runs entirely on your device using a local LLM. + +### Zed ACP (Agent Control Protocol) + +Add siGit as an agent in Zed by adding this to your settings: + +```json +{ + "agent": { + "profiles": { + "sigit": { + "provider": "acp", + "binary": "sigit", + "args": ["--acp"] + } + } + } +} +``` + +--- + +## Platform support + +| Platform | Architecture | Package | +|---|---|---| +| macOS | Apple Silicon (arm64) | `@smbcloud/sigit-darwin-arm64` | +| macOS | Intel (x64) | `@smbcloud/sigit-darwin-x64` | +| Linux | x64 | `@smbcloud/sigit-linux-x64` | +| Linux | arm64 | `@smbcloud/sigit-linux-arm64` | +| Windows | x64 | `@smbcloud/sigit-windows-x64` | +| Windows | arm64 | `@smbcloud/sigit-windows-arm64` | + +--- + +## Links + +- [Source code](https://github.com/getsigit/sigit) +- [Issues](https://github.com/getsigit/sigit/issues) + +## License + +[Apache-2.0](https://github.com/getsigit/sigit/blob/main/LICENSE) + +<p align="center"> + <sub>© 2026 <a href="https://www.smbcloud.xyz">smbCloud</a> · <a href="https://apps.apple.com/se/developer/splitfire-ab/id1831430993">Splitfire AB</a></sub> +</p> \ No newline at end of file
npm/sigit/package.json
+44
new file mode 100644 index 0000000..61bf082 --- /dev/null +++ b/npm/sigit/package.json @@ -0,0 +1,44 @@ +{ + "name": "@smbcloud/sigit", + "version": "0.0.0-dev", + "keywords": [ + "sigit", + "cli", + "ai", + "coding-agent", + "llm", + "on-device", + "smbcloud", + "acp" + ], + "bin": { + "sigit": "lib/index.js" + }, + "files": [ + "lib", + "README.md" + ], + "description": "AI coding agent powered by local LLM via Onde Inference.", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "git+https://github.com/getsigit/sigit.git" + }, + "scripts": { + "typecheck": "tsc --noEmit", + "build": "tsc", + "dev": "npm run build && node lib/index.js" + }, + "devDependencies": { + "@types/node": "^18.15.11", + "typescript": "^5.0.0" + }, + "optionalDependencies": { + "@smbcloud/sigit-darwin-arm64": "0.0.0-dev", + "@smbcloud/sigit-darwin-x64": "0.0.0-dev", + "@smbcloud/sigit-linux-arm64": "0.0.0-dev", + "@smbcloud/sigit-linux-x64": "0.0.0-dev", + "@smbcloud/sigit-windows-arm64": "0.0.0-dev", + "@smbcloud/sigit-windows-x64": "0.0.0-dev" + } +}
npm/sigit/src/index.ts
+43
new file mode 100644 index 0000000..a4d4fd2 --- /dev/null +++ b/npm/sigit/src/index.ts @@ -0,0 +1,43 @@ +#!/usr/bin/env node + +import { spawnSync } from "child_process"; + +/** + * Returns the executable path which is located inside node_modules + * The naming convention is cli-OS-ARCH + * If the platform is win32 or cygwin, executable will include a .exe extension. + * @see https://nodejs.org/api/os.html#osarch + * @see https://nodejs.org/api/os.html#osplatform + * @example "x/xx/node_modules/cli-darwin-arm64" + */ +function getExePath() { + const arch = process.arch; + let os = process.platform as string; + let extension = ""; + if (["win32", "cygwin"].includes(process.platform)) { + os = "windows"; + extension = ".exe"; + } + + try { + // Since the binary will be located inside node_modules, we can simply call require.resolve + return require.resolve( + `@smbcloud/sigit-${os}-${arch}/bin/sigit${extension}`, + ); + } catch (e) { + throw new Error( + `Couldn't find application binary inside node_modules for ${os}-${arch}`, + ); + } +} + +/** + * Runs the application with args using nodejs spawn + */ +function run() { + const args = process.argv.slice(2); + const processResult = spawnSync(getExePath(), args, { stdio: "inherit" }); + process.exit(processResult.status ?? 0); +} + +run();
npm/sigit/tsconfig.json
+12
new file mode 100644 index 0000000..8e6c790 --- /dev/null +++ b/npm/sigit/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "esModuleInterop": true, + "baseUrl": "./", + "outDir": "lib", + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +}
pypi/README.md
+107
new file mode 100644 index 0000000..000e44b --- /dev/null +++ b/pypi/README.md @@ -0,0 +1,107 @@ +<p align="center"> + <strong>siGit Code</strong> +</p> + +<h1 align="center">sigit</h1> + +<p align="center"> + <strong>AI coding agent powered by local LLM via <a href="https://ondeinference.com">Onde Inference</a>.</strong><br> + ACP-compatible agent that runs entirely on your machine — no API keys, no cloud. +</p> + +<p align="center"> + <a href="https://smbcloud.xyz"><img src="https://img.shields.io/badge/smbcloud.xyz-235843?style=flat-square&labelColor=17211D" alt="Website"></a> + <a href="https://pypi.org/project/sigit-code/"><img src="https://img.shields.io/pypi/v/sigit-code?style=flat-square&labelColor=17211D&color=235843" alt="PyPI"></a> + <a href="https://www.npmjs.com/package/@smbcloud/sigit"><img src="https://img.shields.io/npm/v/@smbcloud/sigit?style=flat-square&labelColor=17211D&color=235843" alt="npm"></a> + <a href="https://crates.io/crates/sigit"><img src="https://img.shields.io/crates/v/sigit?style=flat-square&labelColor=17211D&color=235843" alt="Crates.io"></a> + <a href="https://github.com/getsigit/sigit/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-235843?style=flat-square&labelColor=17211D" alt="License"></a> +</p> + +<br> + +--- + +## Install + +```sh +pip install sigit-code +``` + +Installs the native `sigit` binary for your platform — no compiler, no Node.js, no runtime dependencies. + +## Quick start + +### Interactive TUI + +```sh +sigit +``` + +A terminal UI opens where you can chat with a local LLM coding agent directly. + +### Zed editor (ACP agent) + +siGit works as an [ACP-compatible](https://github.com/nicobailon/agent-client-protocol) agent in [Zed](https://zed.dev). Add this to your Zed settings: + +```json +{ + "agent": { + "profiles": { + "sigit": { + "provider": "acp", + "binary": { + "path": "sigit", + "args": ["--acp"] + } + } + } + } +} +``` + +Then select **sigit** as your agent profile in the Zed assistant panel. + +## Other installation methods + +| Method | Command | +|--------|---------| +| npm | `npm install -g @smbcloud/sigit` | +| Homebrew | `brew install getsigit/sigit/sigit` | +| Cargo | `cargo install sigit` | + +### From source + +```sh +git clone https://github.com/getsigit/sigit +cd sigit +cargo build --release +./target/release/sigit +``` + +## Platform support + +Pre-built native binaries ship for every major platform: + +| Platform | Architecture | +|---------------|--------------| +| macOS | arm64, x64 | +| Linux (glibc) | arm64, x64 | +| Windows | arm64, x64 | + +## Source & issues + +This package ships a pre-built native binary. Source lives at +[github.com/getsigit/sigit](https://github.com/getsigit/sigit) — +file bugs and feature requests there. + +## License + +Licensed under **Apache 2.0**. + +- [Apache License 2.0](https://github.com/getsigit/sigit/blob/main/LICENSE) + +--- + +<p align="center"> + <sub>Built by <a href="https://smbcloud.xyz">smbCloud</a> (Splitfire AB) · © 2026</sub> +</p> \ No newline at end of file
pypi/pyproject.toml
+38
new file mode 100644 index 0000000..cd93d3a --- /dev/null +++ b/pypi/pyproject.toml @@ -0,0 +1,38 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "sigit-code" +description = "AI coding agent powered by local LLM via Onde Inference." +readme = "README.md" +requires-python = ">=3.8" +dynamic = ["version"] +license = { text = "Apache-2.0" } +authors = [{ name = "Seto Elkahfi", email = "hej@setoelkahfi.se" }] +keywords = ["sigit", "cli", "ai", "coding-agent", "llm", "on-device"] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "Operating System :: MacOS", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Rust", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "Topic :: Software Development", + "Topic :: Utilities", +] + +[project.urls] +Homepage = "https://smbcloud.xyz" +Documentation = "https://github.com/getsigit/sigit" +Repository = "https://github.com/getsigit/sigit" +Issues = "https://github.com/getsigit/sigit/issues" + +[tool.maturin] +manifest-path = "../Cargo.toml" +bindings = "bin" +strip = true
rust-toolchain.toml
+2
new file mode 100644 index 0000000..292fe49 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "stable"