Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ run = "mise run install && bun scripts/build-release.ts"

[tasks.render-homebrew-formula]
run = "mise run install && bun scripts/render-homebrew-formula.ts"

[tasks.render-chocolatey-package]
run = "mise run install && bun scripts/render-chocolatey-package.ts"
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Skillet installs, discovers, and updates `SKILL.md`-based skills across supporte
| --- | --- | --- |
| Binary release | Download from GitHub Releases | Planned |
| Homebrew | `brew install skillet` | Configured |
| Chocolatey | `choco install skillet` | Planned |
| Chocolatey | `choco install skillet` | Configured |
| winget | `winget install skillet` | Planned |
| npm / npx | `npx skillet ...` | Planned |
| Docker | `docker run ... skillet ...` | Planned |
Expand Down Expand Up @@ -142,3 +142,4 @@ AGENTS instructions live in `AGENTS.md`.
Distribution docs:

- Homebrew: `docs/distribution/homebrew.md`
- Chocolatey: `docs/distribution/chocolatey.md`
31 changes: 31 additions & 0 deletions docs/distribution/chocolatey.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Chocolatey Distribution

Skillet ships a Chocolatey package for Windows CLI installs.

## Release Flow

1. Build release artifacts and checksums.
2. Render `packaging/chocolatey/` from `SHA256SUMS`.
3. Build and push the `.nupkg` with `choco pack` and `choco push`.

## Commands

```bash
mise run build -- --targets=windows-x64
bun scripts/write-checksums.ts
mise run render-chocolatey-package -- --version <version>
```

Generate package and publish:

```powershell
cd packaging/chocolatey
choco pack
choco push skillet.<version>.nupkg --source https://push.chocolatey.org/
```

User install command:

```powershell
choco install skillet
```
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"dev": "bun src/cli.ts",
"test": "vitest run",
"build": "bun scripts/build-release.ts",
"render:homebrew": "bun scripts/render-homebrew-formula.ts"
"render:homebrew": "bun scripts/render-homebrew-formula.ts",
"render:choco": "bun scripts/render-chocolatey-package.ts"
},
"dependencies": {
"cac": "^6.7.14",
Expand Down
17 changes: 17 additions & 0 deletions packaging/chocolatey/skillet.nuspec
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>skillet</id>
<version>0.0.0</version>
<title>skillet</title>
<authors>echohello-dev</authors>
<projectUrl>https://github.com/echohello-dev/skillet</projectUrl>
<packageSourceUrl>https://github.com/echohello-dev/skillet</packageSourceUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Portable CLI for managing agent skills.</description>
<tags>cli skills agent</tags>
</metadata>
<files>
<file src="tools\**" target="tools" />
</files>
</package>
10 changes: 10 additions & 0 deletions packaging/chocolatey/tools/chocolateyinstall.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
$ErrorActionPreference = 'Stop'

$packageName = 'skillet'
$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$binaryPath = Join-Path $toolsDir 'skillet.exe'
$url64 = 'https://github.com/echohello-dev/skillet/releases/download/v0.0.0/skillet-windows-x64.exe'
$checksum64 = '51cddefde243f0f27e501ca420d5e1d1b9cad548dc9884ea4052d2915afef179'

Get-ChocolateyWebFile -PackageName $packageName -FileFullPath $binaryPath -Url64bit $url64 -Checksum64 $checksum64 -ChecksumType64 'sha256'
Install-BinFile -Name 'skillet' -Path $binaryPath
2 changes: 2 additions & 0 deletions packaging/chocolatey/tools/chocolateyuninstall.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
$ErrorActionPreference = 'Stop'
Uninstall-BinFile -Name 'skillet'
63 changes: 63 additions & 0 deletions scripts/render-chocolatey-package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import fs from "node:fs";
import path from "node:path";
import { parseSha256Sums } from "../src/distribution/checksums";
import { renderChocolateyPackageFiles } from "../src/distribution/chocolatey";

function main(): void {
const args = process.argv.slice(2);
const version = readArg(args, "--version") ?? readVersionFromPackageJson();
const checksumsPath = path.resolve(readArg(args, "--checksums") ?? "dist/SHA256SUMS");
const outputDir = path.resolve(readArg(args, "--output-dir") ?? "packaging/chocolatey");
const releaseUrlBase =
readArg(args, "--release-url-base") ??
`https://github.com/echohello-dev/skillet/releases/download/v${version}`;

if (!fs.existsSync(checksumsPath)) {
throw new Error(`Checksums file not found: ${checksumsPath}`);
}

const checksums = parseSha256Sums(fs.readFileSync(checksumsPath, "utf8"));
const files = renderChocolateyPackageFiles({ version, releaseUrlBase, checksumsByArtifact: checksums });

fs.mkdirSync(path.join(outputDir, "tools"), { recursive: true });
fs.writeFileSync(path.join(outputDir, "skillet.nuspec"), files.nuspec);
fs.writeFileSync(path.join(outputDir, "tools", "chocolateyinstall.ps1"), files.installScript);
fs.writeFileSync(path.join(outputDir, "tools", "chocolateyuninstall.ps1"), files.uninstallScript);
console.log(`Wrote Chocolatey package files to ${outputDir}`);
}

function readArg(args: string[], name: string): string | undefined {
for (let i = 0; i < args.length; i += 1) {
const token = args[i];
if (token === name) {
const next = args[i + 1];
if (!next || next.startsWith("--")) {
throw new Error(`Missing value for ${name}`);
}
return next;
}

const prefix = `${name}=`;
if (token.startsWith(prefix)) {
const value = token.slice(prefix.length);
if (value.length === 0) {
throw new Error(`Missing value for ${name}`);
}
return value;
}
}

return undefined;
}

function readVersionFromPackageJson(): string {
const packageJsonPath = path.resolve("package.json");
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as { version?: string };
if (!packageJson.version || packageJson.version.length === 0) {
throw new Error("package.json version is missing");
}

return packageJson.version;
}

main();
63 changes: 63 additions & 0 deletions src/distribution/chocolatey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
const WINDOWS_X64_ARTIFACT = "skillet-windows-x64.exe";

export type ChocolateyPackageOptions = {
version: string;
releaseUrlBase: string;
checksumsByArtifact: Map<string, string>;
};

export type ChocolateyPackageFiles = {
nuspec: string;
installScript: string;
uninstallScript: string;
};

export function renderChocolateyPackageFiles(options: ChocolateyPackageOptions): ChocolateyPackageFiles {
const releaseUrlBase = options.releaseUrlBase.replace(/\/+$/, "");
const downloadUrl = `${releaseUrlBase}/${WINDOWS_X64_ARTIFACT}`;
const checksum = requireChecksum(options.checksumsByArtifact, WINDOWS_X64_ARTIFACT);

return {
nuspec: `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2015/06/nuspec.xsd">
<metadata>
<id>skillet</id>
<version>${options.version}</version>
<title>skillet</title>
<authors>echohello-dev</authors>
<projectUrl>https://github.com/echohello-dev/skillet</projectUrl>
<packageSourceUrl>https://github.com/echohello-dev/skillet</packageSourceUrl>
<requireLicenseAcceptance>false</requireLicenseAcceptance>
<description>Portable CLI for managing agent skills.</description>
<tags>cli skills agent</tags>
</metadata>
<files>
<file src="tools\\**" target="tools" />
</files>
</package>
`,
installScript: `$ErrorActionPreference = 'Stop'

$packageName = 'skillet'
$toolsDir = Split-Path -Parent $MyInvocation.MyCommand.Definition
$binaryPath = Join-Path $toolsDir 'skillet.exe'
$url64 = '${downloadUrl}'
$checksum64 = '${checksum}'

Get-ChocolateyWebFile -PackageName $packageName -FileFullPath $binaryPath -Url64bit $url64 -Checksum64 $checksum64 -ChecksumType64 'sha256'
Install-BinFile -Name 'skillet' -Path $binaryPath
`,
uninstallScript: `$ErrorActionPreference = 'Stop'
Uninstall-BinFile -Name 'skillet'
`,
};
}

function requireChecksum(checksumsByArtifact: Map<string, string>, artifactName: string): string {
const checksum = checksumsByArtifact.get(artifactName);
if (!checksum) {
throw new Error(`Missing checksum for artifact: ${artifactName}`);
}

return checksum;
}
23 changes: 23 additions & 0 deletions tests/distribution/chocolatey.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it } from "vitest";
import { renderChocolateyPackageFiles } from "../../src/distribution/chocolatey";

describe("renderChocolateyPackageFiles", () => {
it("renders nuspec and install scripts from checksum data", () => {
const files = renderChocolateyPackageFiles({
version: "1.2.3",
releaseUrlBase: "https://github.com/echohello-dev/skillet/releases/download/v1.2.3",
checksumsByArtifact: new Map([
["skillet-windows-x64.exe", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"],
]),
});

expect(files.nuspec).toContain("<version>1.2.3</version>");
expect(files.installScript).toContain(
"$url64 = 'https://github.com/echohello-dev/skillet/releases/download/v1.2.3/skillet-windows-x64.exe'",
);
expect(files.installScript).toContain(
"$checksum64 = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'",
);
expect(files.uninstallScript).toContain("Uninstall-BinFile -Name 'skillet'");
});
});