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"