diff --git a/.gitignore b/.gitignore index 4616c4e..f6938d2 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ model/.venv/ model/__pycache__/ dist/ +npm/dist/ diff --git a/npm/README.md b/npm/README.md new file mode 100644 index 0000000..c013267 --- /dev/null +++ b/npm/README.md @@ -0,0 +1,17 @@ +# whittle + +Cut your AI agent's token bill. Lose nothing that matters. + +This package installs the prebuilt `whittle` binary for your platform (macOS and +Linux, arm64 and amd64). It is the npm distribution of +[github.com/firstops-dev/whittle](https://github.com/firstops-dev/whittle); the +same binaries also ship via `go install` and Homebrew. + +```sh +npx @firstops/whittle setup # one command: hook + local daemon + sidecar +# or install globally: +npm install -g @firstops/whittle && whittle setup +``` + +Full documentation, benchmarks, and the fidelity contract: +[the repository](https://github.com/firstops-dev/whittle). diff --git a/npm/bin/whittle.js b/npm/bin/whittle.js new file mode 100755 index 0000000..96ed26a --- /dev/null +++ b/npm/bin/whittle.js @@ -0,0 +1,40 @@ +#!/usr/bin/env node +// npx/npm shim: resolves the platform binary package and execs the real whittle. +// The Go binary is unaware this wrapper exists; go install and brew are +// unaffected alternative channels. +"use strict"; +const { spawnSync } = require("child_process"); + +const SCOPE = "@firstops"; // keep in sync with npm/prepare.sh +const goArch = { x64: "amd64", arm64: "arm64" }[process.arch]; +const goOS = { darwin: "darwin", linux: "linux" }[process.platform]; + +if (!goOS || !goArch) { + console.error( + `whittle: no prebuilt binary for ${process.platform}-${process.arch}.\n` + + `Install from source instead:\n` + + ` go install github.com/firstops-dev/whittle/cmd/whittle@latest\n` + + `or download a release: https://github.com/firstops-dev/whittle/releases` + ); + process.exit(1); +} + +let bin; +try { + bin = require.resolve(`${SCOPE}/whittle-${goOS}-${goArch}/bin/whittle`); +} catch { + console.error( + `whittle: platform package ${SCOPE}/whittle-${goOS}-${goArch} is not installed.\n` + + `This usually means npm skipped optional dependencies. Try:\n` + + ` npm install --include=optional\n` + + `or use: go install github.com/firstops-dev/whittle/cmd/whittle@latest` + ); + process.exit(1); +} + +const r = spawnSync(bin, process.argv.slice(2), { stdio: "inherit" }); +if (r.error) { + console.error(`whittle: failed to start binary: ${r.error.message}`); + process.exit(1); +} +process.exit(r.status === null ? 1 : r.status); diff --git a/npm/package.json b/npm/package.json new file mode 100644 index 0000000..bfd2631 --- /dev/null +++ b/npm/package.json @@ -0,0 +1,18 @@ +{ + "name": "@firstops/whittle", + "version": "0.0.0-managed-by-prepare-sh", + "description": "Cut your AI agent's token bill. Lose nothing that matters. Write-time tool-output compression and policy-based model routing for Claude Code, local and fail-open.", + "keywords": ["claude", "claude-code", "ai-agents", "context", "compression", "llm", "router", "tokens"], + "license": "Apache-2.0", + "repository": { "type": "git", "url": "git+https://github.com/firstops-dev/whittle.git" }, + "homepage": "https://github.com/firstops-dev/whittle#readme", + "bin": { "whittle": "bin/whittle.js" }, + "files": ["bin/whittle.js", "README.md"], + "engines": { "node": ">=16" }, + "optionalDependencies": { + "@firstops/whittle-darwin-arm64": "0.0.0-managed-by-prepare-sh", + "@firstops/whittle-darwin-amd64": "0.0.0-managed-by-prepare-sh", + "@firstops/whittle-linux-arm64": "0.0.0-managed-by-prepare-sh", + "@firstops/whittle-linux-amd64": "0.0.0-managed-by-prepare-sh" + } +} diff --git a/npm/prepare.sh b/npm/prepare.sh new file mode 100755 index 0000000..5a79cb2 --- /dev/null +++ b/npm/prepare.sh @@ -0,0 +1,51 @@ +#!/bin/sh +# prepare.sh [--publish] +# Builds the npm packages for a tagged release: downloads the goreleaser assets, +# verifies checksums, assembles 4 platform packages + the meta package under +# npm/dist/, and (with --publish) publishes all five. Version = the git tag +# without the v prefix, so npm always mirrors a real GitHub release. +set -eu +SCOPE="@firstops" # keep in sync with npm/bin/whittle.js +V="${1:?usage: prepare.sh [--publish]}" +PUBLISH="${2:-}" +cd "$(dirname "$0")" + +work=$(mktemp -d) && trap 'rm -rf "$work"' EXIT +echo "downloading release v$V assets..." +gh release download "v$V" -R firstops-dev/whittle -p '*.tar.gz' -p checksums.txt -D "$work" +(cd "$work" && grep '\.tar\.gz' checksums.txt | shasum -a 256 -c - >/dev/null) && echo "checksums verified" + +rm -rf dist && mkdir -p dist +for plat in darwin_arm64 darwin_amd64 linux_arm64 linux_amd64; do + os="${plat%_*}"; arch="${plat#*_}" + pkg="dist/whittle-$os-$arch" + mkdir -p "$pkg/bin" + tar -xzf "$work/whittle_${V}_${plat}.tar.gz" -C "$pkg/bin" whittle + chmod +x "$pkg/bin/whittle" + cat > "$pkg/package.json" < dist/whittle/package.json +cp bin/whittle.js dist/whittle/bin/whittle.js && chmod +x dist/whittle/bin/whittle.js +cp README.md dist/whittle/README.md +echo "assembled: $(ls dist)" + +if [ "$PUBLISH" = "--publish" ]; then + for d in dist/whittle-*; do (cd "$d" && npm publish --access public); done + (cd dist/whittle && npm publish --access public) + echo "published v$V (platform packages first, meta last)" +else + echo "dry run complete. To publish: sh npm/prepare.sh $V --publish (requires npm login)" +fi