@splitfireai / agent / commits / 2f65da6

Add winget package manifests and release workflow

- packaging/winget/SplitFireAI.splitfire-agent.{yaml,installer,locale.en-US}.yaml.in: manifest templates with @VERSION@/@SHA_X64@/@RELEASE_DATE@ placeholders - .github/workflows/release.yml: append checksums job that concatenates all per-platform SHA256 files into a combined checksums.txt and uploads it as a release asset (required by the winget workflow) - .github/workflows/release-winget.yml: manually triggered workflow that extracts the x64 SHA256 from checksums.txt, detects whether the package already exists in winget-pkgs, and either submits a new package via `wingetcreate submit` or updates an existing one via `wingetcreate update` Co-Authored-By: siGit Code v1.6.7-acp <noreply@sigit.si>

Seto committed Oct 11, 2026 at 22:48 UTC 2f65da67ece5b0e716d2abbdb7135a16791bfc95
5 files changed +269
.github/workflows/release-winget.yml new
+185
@@ -0,0 +1,185 @@
1 +name: winget Release
2 +
3 +# Dispatched manually once the GitHub release and its checksums.txt exist.
4 +#
5 +# winget manifests live in microsoft/winget-pkgs, and `wingetcreate update`
6 +# only works on a package that is already there. So this workflow branches:
7 +#
8 +# - package already in winget-pkgs -> `wingetcreate update`, which rewrites
9 +# the URL and re-hashes the installer for us.
10 +# - package not there yet -> render the manifests in packaging/winget/ and
11 +# `wingetcreate submit` them as a new package.
12 +#
13 +# The second path only runs once, but keeping it here means a first release
14 +# does not need someone to sit at a Windows box running `wingetcreate new`
15 +# interactively.
16 +#
17 +# splitfire-agent ships a single Windows x64 zip (splitfire-agent-windows-x64.zip).
18 +# The SHA256 is extracted from the combined checksums.txt that the release
19 +# workflow uploads after all build jobs finish.
20 +on:
21 + workflow_dispatch:
22 + inputs:
23 + tag:
24 + description: "Release tag (e.g. v1.1.1)"
25 + required: true
26 +
27 +concurrency:
28 + # Queue a re-dispatched tag behind the original instead of cancelling it:
29 + # a cancelled mid-publish run can leave a channel half-updated, whereas a
30 + # queued duplicate just hits the "already published" check and exits clean.
31 + group: ${{ github.workflow }}-${{ github.event.inputs.tag || github.ref_name }}
32 + cancel-in-progress: false
33 +
34 +permissions:
35 + contents: read
36 +
37 +env:
38 + REPO: SplitFireAI/splitfire-agent
39 + PACKAGE_IDENTIFIER: SplitFireAI.splitfire-agent
40 +
41 +jobs:
42 + submit-winget-manifest:
43 + name: Submit winget manifest
44 + # wingetcreate is a Windows-only tool.
45 + runs-on: windows-latest
46 +
47 + steps:
48 + - name: Check out repository
49 + uses: actions/checkout@v4
50 +
51 + - name: Resolve tag, version and release date
52 + id: release
53 + shell: bash
54 + env:
55 + GH_TOKEN: ${{ github.token }}
56 + run: |
57 + TAG="${{ github.event.inputs.tag }}"
58 + VERSION="${TAG#v}"
59 + # wingetcreate wants ReleaseDate as YYYY-MM-DD.
60 + DATE=$(gh release view "$TAG" --repo "$REPO" --json publishedAt --jq '.publishedAt[0:10]')
61 +
62 + echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
63 + echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
64 + echo "date=${DATE}" >> "$GITHUB_OUTPUT"
65 +
66 + - name: Extract SHA256 from checksums.txt
67 + id: sha
68 + shell: bash
69 + env:
70 + GH_TOKEN: ${{ github.token }}
71 + run: |
72 + TAG="${{ steps.release.outputs.tag }}"
73 + mkdir -p artifacts
74 + gh release download "$TAG" --repo "$REPO" --pattern "checksums.txt" --dir artifacts/
75 +
76 + # checksums.txt lines: "<hash> <filename>"
77 + # The schema wants 64 uppercase hex characters.
78 + SHA_X64=$(grep 'splitfire-agent-windows-x64\.zip' artifacts/checksums.txt \
79 + | awk '{print $1}' | tr '[:lower:]' '[:upper:]')
80 +
81 + if [ -z "$SHA_X64" ]; then
82 + echo "::error::Could not find splitfire-agent-windows-x64.zip in checksums.txt"
83 + exit 1
84 + fi
85 +
86 + echo "sha_x64=${SHA_X64}" >> "$GITHUB_OUTPUT"
87 +
88 + - name: Check whether the package is already in winget-pkgs
89 + id: exists
90 + shell: bash
91 + env:
92 + GH_TOKEN: ${{ github.token }}
93 + run: |
94 + # Manifests are filed under the lowercased first letter of the
95 + # publisher, e.g. manifests/s/SplitFireAI/splitfire-agent.
96 + PUBLISHER="${PACKAGE_IDENTIFIER%%.*}"
97 + NAME="${PACKAGE_IDENTIFIER#*.}"
98 + FIRST=$(echo "${PUBLISHER:0:1}" | tr '[:upper:]' '[:lower:]')
99 + PATH_IN_REPO="manifests/${FIRST}/${PUBLISHER}/${NAME}"
100 +
101 + if gh api "repos/microsoft/winget-pkgs/contents/${PATH_IN_REPO}" >/dev/null 2>&1; then
102 + echo "Found ${PACKAGE_IDENTIFIER} at ${PATH_IN_REPO}; submitting an update."
103 + echo "found=true" >> "$GITHUB_OUTPUT"
104 + else
105 + echo "No ${PACKAGE_IDENTIFIER} at ${PATH_IN_REPO}; submitting it as a new package."
106 + echo "found=false" >> "$GITHUB_OUTPUT"
107 + fi
108 +
109 + - name: Render manifests for a first submission
110 + if: steps.exists.outputs.found == 'false'
111 + shell: bash
112 + run: |
113 + TAG="${{ steps.release.outputs.tag }}"
114 + VERSION="${{ steps.release.outputs.version }}"
115 + SHA_X64="${{ steps.sha.outputs.sha_x64 }}"
116 + RELEASE_DATE="${{ steps.release.outputs.date }}"
117 +
118 + mkdir -p manifests
119 +
120 + for template in packaging/winget/*.yaml.in; do
121 + out="manifests/$(basename "$template" .in)"
122 + sed \
123 + -e "s|@VERSION@|${VERSION}|g" \
124 + -e "s|@TAG@|${TAG}|g" \
125 + -e "s|@REPO@|${REPO}|g" \
126 + -e "s|@SHA_X64@|${SHA_X64}|g" \
127 + -e "s|@RELEASE_DATE@|${RELEASE_DATE}|g" \
128 + "$template" > "$out"
129 +
130 + echo "--- ${out} ---"
131 + cat "$out"
132 + done
133 +
134 + - name: Download wingetcreate
135 + shell: pwsh
136 + run: Invoke-WebRequest -Uri "https://aka.ms/wingetcreate/latest" -OutFile wingetcreate.exe
137 +
138 + - name: Submit new package
139 + if: steps.exists.outputs.found == 'false'
140 + shell: pwsh
141 + env:
142 + WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }}
143 + run: |
144 + if (-not $env:WINGET_CREATE_GITHUB_TOKEN) {
145 + Write-Error "WINGET_TOKEN is not set. It needs a PAT with public_repo scope so wingetcreate can fork microsoft/winget-pkgs and open the manifest PR."
146 + }
147 +
148 + $version = "${{ steps.release.outputs.version }}"
149 +
150 + # --no-open because the runner has no browser to open the PR in.
151 + .\wingetcreate.exe submit manifests `
152 + --prtitle "New package: $env:PACKAGE_IDENTIFIER version $version" `
153 + --no-open
154 +
155 + if ($LASTEXITCODE -ne 0) {
156 + Write-Error "wingetcreate submit failed with exit code $LASTEXITCODE"
157 + }
158 +
159 + - name: Submit manifest update
160 + if: steps.exists.outputs.found == 'true'
161 + shell: pwsh
162 + env:
163 + WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.WINGET_TOKEN }}
164 + run: |
165 + if (-not $env:WINGET_CREATE_GITHUB_TOKEN) {
166 + Write-Error "WINGET_TOKEN is not set. It needs a PAT with public_repo scope so wingetcreate can fork microsoft/winget-pkgs and open the manifest PR."
167 + }
168 +
169 + $tag = "${{ steps.release.outputs.tag }}"
170 + $version = "${{ steps.release.outputs.version }}"
171 + $base = "https://github.com/${env:REPO}/releases/download/$tag"
172 +
173 + # Single x64 zip installer — no arch suffix needed in the URL hint.
174 + .\wingetcreate.exe update $env:PACKAGE_IDENTIFIER `
175 + --version $version `
176 + --urls "$base/splitfire-agent-windows-x64.zip|x64" `
177 + --release-notes-url "https://github.com/${env:REPO}/releases/tag/$tag" `
178 + --release-date "${{ steps.release.outputs.date }}" `
179 + --prtitle "Update $env:PACKAGE_IDENTIFIER to version $version" `
180 + --submit `
181 + --no-open
182 +
183 + if ($LASTEXITCODE -ne 0) {
184 + Write-Error "wingetcreate failed with exit code $LASTEXITCODE"
185 + }
.github/workflows/release.yml
+29
@@ -48,3 +48,32 @@ jobs:
48 gh release create "$GITHUB_REF_NAME" --verify-tag --generate-notes \
49 || gh release view "$GITHUB_REF_NAME" >/dev/null
50 gh release upload "$GITHUB_REF_NAME" "$asset" --clobber
51 +
52 + checksums:
53 + name: Publish checksums.txt
54 + needs: build
55 + runs-on: ubuntu-latest
56 + steps:
57 + - name: Download all release assets
58 + env:
59 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
60 + run: |
61 + mkdir -p artifacts
62 + gh release download "$GITHUB_REF_NAME" \
63 + --repo "$GITHUB_REPOSITORY" \
64 + --pattern "splitfire-agent-*" \
65 + --dir artifacts/
66 +
67 + - name: Generate checksums.txt
68 + run: |
69 + cd artifacts
70 + sha256sum splitfire-agent-* > checksums.txt
71 + cat checksums.txt
72 +
73 + - name: Upload checksums.txt
74 + env:
75 + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
76 + run: |
77 + gh release upload "$GITHUB_REF_NAME" \
78 + --repo "$GITHUB_REPOSITORY" \
79 + artifacts/checksums.txt --clobber
packaging/winget/SplitFireAI.splitfire-agent.installer.yaml.in new
+17
@@ -0,0 +1,17 @@
1 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.installer.1.6.0.schema.json
2 +PackageIdentifier: SplitFireAI.splitfire-agent
3 +PackageVersion: @VERSION@
4 +InstallerType: zip
5 +# `NestedInstallerType` and `NestedInstallerFiles` tell winget where to find
6 +# the exe inside the zip and what command shim to place on PATH.
7 +NestedInstallerType: portable
8 +NestedInstallerFiles:
9 + - RelativeFilePath: splitfire-agent.exe
10 + PortableCommandAlias: splitfire-agent
11 +ReleaseDate: "@RELEASE_DATE@"
12 +Installers:
13 + - Architecture: x64
14 + InstallerUrl: https://github.com/@REPO@/releases/download/@TAG@/splitfire-agent-windows-x64.zip
15 + InstallerSha256: @SHA_X64@
16 +ManifestType: installer
17 +ManifestVersion: 1.6.0
packaging/winget/SplitFireAI.splitfire-agent.locale.en-US.yaml.in new
+32
@@ -0,0 +1,32 @@
1 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.defaultLocale.1.6.0.schema.json
2 +PackageIdentifier: SplitFireAI.splitfire-agent
3 +PackageVersion: @VERSION@
4 +PackageLocale: en-US
5 +Publisher: SplitFireAI
6 +PublisherUrl: https://github.com/SplitFireAI
7 +PublisherSupportUrl: https://github.com/@REPO@/issues
8 +PackageName: SplitFire Agent
9 +PackageUrl: https://github.com/@REPO@
10 +License: MIT OR Apache-2.0
11 +LicenseUrl: https://github.com/@REPO@/blob/main/LICENSE
12 +ShortDescription: Music-domain ACP agent for theory, production, industry, culture and live shows.
13 +Description: |-
14 + SplitFire Agent is a music-domain ACP-compatible coding agent backed by any
15 + OpenAI-compatible chat completions API. It covers music theory, production,
16 + industry knowledge, culture, and live shows, and integrates with editors such
17 + as Zed and VS Code via the Agent Client Protocol (ACP).
18 +Moniker: splitfire-agent
19 +Tags:
20 + - ai
21 + - acp
22 + - agent
23 + - music
24 + - inference
25 + - llm
26 + - splitfire
27 +ReleaseNotesUrl: https://github.com/@REPO@/releases/tag/@TAG@
28 +Documentations:
29 + - DocumentLabel: Readme
30 + DocumentUrl: https://github.com/@REPO@/blob/main/README.md
31 +ManifestType: defaultLocale
32 +ManifestVersion: 1.6.0
packaging/winget/SplitFireAI.splitfire-agent.yaml.in new
+6
@@ -0,0 +1,6 @@
1 +# yaml-language-server: $schema=https://aka.ms/winget-manifest.version.1.6.0.schema.json
2 +PackageIdentifier: SplitFireAI.splitfire-agent
3 +PackageVersion: @VERSION@
4 +DefaultLocale: en-US
5 +ManifestType: version
6 +ManifestVersion: 1.6.0