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