Skip to content

Commit c6cd6d9

Browse files
committed
ci: add CI/release workflows and npm binary distribution
1 parent fa36957 commit c6cd6d9

8 files changed

Lines changed: 293 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
ci:
14+
runs-on: ubuntu-latest
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- uses: actions/setup-go@v5
19+
with:
20+
go-version-file: go.mod
21+
22+
- run: go vet ./...
23+
24+
- run: make test
25+
26+
- run: make build

.github/workflows/release.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Release
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "Version to release (e.g. v0.45.4)"
8+
required: true
9+
type: string
10+
11+
permissions:
12+
contents: write
13+
id-token: write
14+
15+
jobs:
16+
release:
17+
runs-on: ubuntu-latest
18+
env:
19+
VERSION: ${{ inputs.version }}
20+
steps:
21+
- uses: actions/checkout@v4
22+
with:
23+
fetch-depth: 0
24+
25+
- uses: actions/setup-go@v5
26+
with:
27+
go-version-file: go.mod
28+
29+
- run: make dist
30+
31+
- uses: actions/setup-node@v4
32+
with:
33+
node-version: "24"
34+
registry-url: "https://registry.npmjs.org"
35+
36+
- name: Publish to npm
37+
working-directory: npm
38+
run: |
39+
SEMVER="${VERSION#v}"
40+
npm version "$SEMVER" --no-git-tag-version
41+
if [[ "$SEMVER" == *-* ]]; then
42+
npm publish --provenance --access public --tag next
43+
else
44+
npm publish --provenance --access public
45+
fi
46+
47+
- name: Create and push tag
48+
run: |
49+
git tag "$VERSION"
50+
git push origin "$VERSION"
51+
52+
- name: Create GitHub Release
53+
uses: softprops/action-gh-release@v2
54+
with:
55+
tag_name: ${{ inputs.version }}
56+
generate_release_notes: true
57+
files: |
58+
dist/intercept-*.tar.gz
59+
dist/intercept-*.zip
60+
dist/checksums.txt

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
.claude
2-
intercept
2+
/intercept
33
dist/

Makefile

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ HOSTARCH = $(shell go env GOARCH)
88

99
PLATFORMS = linux-amd64 linux-arm64 darwin-amd64 darwin-arm64 windows-amd64 windows-arm64
1010

11-
.PHONY: build all clean test lint $(PLATFORMS)
11+
.PHONY: build all dist clean test lint $(PLATFORMS)
1212

1313
build:
1414
CGO_ENABLED=0 GOOS=$(HOSTOS) GOARCH=$(HOSTARCH) \
@@ -27,6 +27,19 @@ endef
2727

2828
$(foreach platform,$(PLATFORMS),$(eval $(call build-platform,$(platform))))
2929

30+
dist: all
31+
@for dir in dist/*/; do \
32+
name=$$(basename $$dir); \
33+
os=$$(echo $$name | cut -d_ -f1); \
34+
arch=$$(echo $$name | cut -d_ -f2); \
35+
if [ "$$os" = "windows" ]; then \
36+
(cd $$dir && zip -q ../../dist/intercept-$$os-$$arch.zip *); \
37+
else \
38+
tar -czf dist/intercept-$$os-$$arch.tar.gz -C $$dir .; \
39+
fi \
40+
done
41+
@cd dist && sha256sum intercept-*.tar.gz intercept-*.zip > checksums.txt
42+
3043
clean:
3144
rm -rf dist/
3245

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,22 @@ Intercept is a transparent proxy that sits between an AI agent and an MCP (Model
2020

2121
## Install
2222

23+
**npm:**
24+
25+
```sh
26+
npm install -g @policylayer/intercept
27+
```
28+
29+
**Go:**
30+
2331
```sh
2432
go install github.com/policylayer/intercept@latest
2533
```
2634

35+
**Pre-built binaries:**
36+
37+
Download from [GitHub Releases](https://github.com/policylayer/intercept/releases) and place the binary on your PATH.
38+
2739
## Quick start
2840

2941
**1. Generate a policy scaffold from a running MCP server:**

npm/bin/intercept

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
#!/bin/sh
2+
set -e
3+
4+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5+
BINARY="$SCRIPT_DIR/intercept"
6+
7+
if [ "$(uname -o 2>/dev/null)" = "Msys" ] || [ "$(uname -o 2>/dev/null)" = "Cygwin" ]; then
8+
BINARY="$SCRIPT_DIR/intercept.exe"
9+
fi
10+
11+
if [ ! -f "$BINARY" ] || [ "$BINARY" = "$0" ]; then
12+
echo "Error: intercept binary not found. Run 'npm install' or 'node install.js' first." >&2
13+
exit 1
14+
fi
15+
16+
exec "$BINARY" "$@"

npm/install.js

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
#!/usr/bin/env node
2+
"use strict";
3+
4+
const https = require("https");
5+
const http = require("http");
6+
const fs = require("fs");
7+
const path = require("path");
8+
const { execSync } = require("child_process");
9+
const PLATFORM_MAP = {
10+
linux: "linux",
11+
darwin: "darwin",
12+
win32: "windows",
13+
};
14+
15+
const ARCH_MAP = {
16+
x64: "amd64",
17+
arm64: "arm64",
18+
};
19+
20+
function getPackageVersion() {
21+
const pkg = JSON.parse(
22+
fs.readFileSync(path.join(__dirname, "package.json"), "utf8")
23+
);
24+
return pkg.version;
25+
}
26+
27+
function getDownloadUrl(version, os, arch) {
28+
const ext = os === "windows" ? "zip" : "tar.gz";
29+
return `https://github.com/policylayer/intercept/releases/download/v${version}/intercept-${os}-${arch}.${ext}`;
30+
}
31+
32+
function fetch(url, redirects = 0) {
33+
return new Promise((resolve, reject) => {
34+
if (redirects > 10) {
35+
return reject(new Error("Too many redirects"));
36+
}
37+
const client = url.startsWith("https") ? https : http;
38+
client
39+
.get(url, { headers: { "User-Agent": "policylayer-intercept-npm" } }, (res) => {
40+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
41+
return fetch(res.headers.location, redirects + 1).then(resolve, reject);
42+
}
43+
if (res.statusCode !== 200) {
44+
return reject(new Error(`Download failed: HTTP ${res.statusCode} from ${url}`));
45+
}
46+
const chunks = [];
47+
res.on("data", (chunk) => chunks.push(chunk));
48+
res.on("end", () => resolve(Buffer.concat(chunks)));
49+
res.on("error", reject);
50+
})
51+
.on("error", reject);
52+
});
53+
}
54+
55+
function extractTarGz(buffer, destDir) {
56+
const tmpFile = path.join(destDir, "_tmp.tar.gz");
57+
fs.writeFileSync(tmpFile, buffer);
58+
try {
59+
execSync(`tar -xzf "${tmpFile}" -C "${destDir}"`, { stdio: "ignore" });
60+
} finally {
61+
fs.unlinkSync(tmpFile);
62+
}
63+
}
64+
65+
function extractZip(buffer, destDir) {
66+
const tmpFile = path.join(destDir, "_tmp.zip");
67+
fs.writeFileSync(tmpFile, buffer);
68+
try {
69+
// PowerShell is available on all supported Windows versions
70+
execSync(
71+
`powershell -NoProfile -Command "Expand-Archive -Force '${tmpFile}' '${destDir}'"`,
72+
{ stdio: "ignore" }
73+
);
74+
} finally {
75+
fs.unlinkSync(tmpFile);
76+
}
77+
}
78+
79+
async function main() {
80+
const platform = PLATFORM_MAP[process.platform];
81+
const arch = ARCH_MAP[process.arch];
82+
83+
if (!platform || !arch) {
84+
console.error(
85+
`Unsupported platform: ${process.platform}-${process.arch}`
86+
);
87+
process.exit(1);
88+
}
89+
90+
const version = getPackageVersion();
91+
if (version === "0.0.0") {
92+
console.error(
93+
"Package version is 0.0.0. Binary download is only available for published releases."
94+
);
95+
process.exit(1);
96+
}
97+
98+
const url = getDownloadUrl(version, platform, arch);
99+
const binDir = path.join(__dirname, "bin");
100+
const binaryName = platform === "windows" ? "intercept.exe" : "intercept";
101+
const binaryPath = path.join(binDir, binaryName);
102+
103+
console.log(`Downloading intercept v${version} for ${platform}/${arch}...`);
104+
105+
try {
106+
const buffer = await fetch(url);
107+
108+
fs.mkdirSync(binDir, { recursive: true });
109+
110+
if (platform === "windows") {
111+
extractZip(buffer, binDir);
112+
} else {
113+
extractTarGz(buffer, binDir);
114+
}
115+
116+
fs.chmodSync(binaryPath, 0o755);
117+
console.log(`Installed intercept to ${binaryPath}`);
118+
} catch (err) {
119+
console.error(`Failed to install intercept: ${err.message}`);
120+
process.exit(1);
121+
}
122+
}
123+
124+
main();

npm/package.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"name": "@policylayer/intercept",
3+
"version": "0.0.0",
4+
"description": "Policy-as-code enforcement for MCP tool calls",
5+
"license": "Apache-2.0",
6+
"repository": {
7+
"type": "git",
8+
"url": "git+https://github.com/PolicyLayer/Intercept.git",
9+
"directory": "npm"
10+
},
11+
"homepage": "https://github.com/policylayer/intercept",
12+
"keywords": [
13+
"mcp",
14+
"model-context-protocol",
15+
"policy",
16+
"proxy",
17+
"ai",
18+
"security",
19+
"guardrails"
20+
],
21+
"bin": {
22+
"intercept": "bin/intercept"
23+
},
24+
"scripts": {
25+
"postinstall": "node install.js"
26+
},
27+
"os": [
28+
"linux",
29+
"darwin",
30+
"win32"
31+
],
32+
"cpu": [
33+
"x64",
34+
"arm64"
35+
],
36+
"files": [
37+
"bin/",
38+
"install.js"
39+
]
40+
}

0 commit comments

Comments
 (0)