Skip to content
Draft
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
111 changes: 111 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -623,3 +623,114 @@ jobs:
# account-scoped API token, so attestations MUST be disabled here.
attestations: false
skip-existing: true

# ── npm binary shim (@gilamonster/<bin> + newt-agent umbrella) ────────────
#
# Delivers the `newt` + `newt-mcp-server` binaries to npm users the uv/esbuild
# way (newt-agent#1221): one tiny per-platform package carrying just the
# prebuilt binary + os/cpu, per-binary umbrellas `@gilamonster/<bin>` listing
# them as optionalDependencies, and the unscoped `newt-agent` umbrella
# (`npm i -g newt-agent` -> `newt`). No postinstall. Sources + design: npm/.
#
# Ships only what build-binaries produces (-p newt-agent -p newt-mcp-server);
# newt-mcp-data / newt-provider-openai names are reserved but not built yet.
#
# Auth: npm OIDC trusted publishing (NO stored token) — each of the 21 packages
# has a trusted publisher configured on npmjs pointing at
# Gilamonster-Foundation/newt-agent -> release.yml, and id-token: write + npm
# >= 11.5.1 mints a short-lived token. NOTE: this diverges from the PyPI/crates
# jobs above, which deliberately use account-scoped tokens; the maintainer
# chose OIDC for npm. Tag-only (an npm version can't be republished).

build-npm:
name: npm binary (${{ matrix.key }})
if: startsWith(github.ref, 'refs/tags/v')
permissions:
contents: read
id-token: write # npm provenance + OIDC trusted publishing
strategy:
fail-fast: false
matrix:
include:
- { key: darwin-arm64, os: macos-latest, target: aarch64-apple-darwin }
- { key: linux-x64, os: ubuntu-latest, target: x86_64-unknown-linux-gnu }
- { key: win32-x64, os: windows-latest, target: x86_64-pc-windows-msvc }
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing (>= 11.5.1)
run: npm install -g npm@latest
- uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.target }}
- uses: Swatinem/rust-cache@v2
with:
key: npm-${{ matrix.key }}

- name: Derive version from tag
shell: bash
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"

- name: Build binaries
run: cargo build --release --locked --target ${{ matrix.target }} -p newt-agent -p newt-mcp-server

- name: Wrap + publish platform packages
shell: bash
run: |
ext=""; [ "$RUNNER_OS" = "Windows" ] && ext=".exe"
for shim in "@gilamonster/newt" "@gilamonster/newt-mcp-server"; do
bin="${shim##*/}"
out="dist-npm/${bin}-${{ matrix.key }}"
node npm/scripts/build-platform-package.mjs \
--name "$shim" \
--binary "target/${{ matrix.target }}/release/${bin}${ext}" \
--key "${{ matrix.key }}" --version "$VERSION" --out "$out"
( cd "$out"
PKG=$(node -p "require('./package.json').name")
if npm view "$PKG@$VERSION" version >/dev/null 2>&1; then
echo "$PKG@$VERSION already published — skipping"
else
npm publish --provenance --access public
fi )
done

publish-npm-meta:
name: npm shims + umbrella
if: startsWith(github.ref, 'refs/tags/v')
needs: [build-npm] # platform packages must be published first
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "22"
registry-url: "https://registry.npmjs.org"
- name: Upgrade npm for OIDC trusted publishing (>= 11.5.1)
run: npm install -g npm@latest

- name: Derive version from tag
run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV"

- name: Stamp versions across the shim packages
run: node npm/scripts/sync-versions.mjs --version "$VERSION"

- name: Publish shims + umbrella
shell: bash
# Order matters: the newt-agent umbrella depends on @gilamonster/newt.
run: |
for dir in npm/newt npm/newt-mcp-server npm/newt-agent; do
( cd "$dir"
PKG=$(node -p "require('./package.json').name")
if npm view "$PKG@$VERSION" version >/dev/null 2>&1; then
echo "$PKG@$VERSION already published — skipping"
else
npm publish --provenance --access public
fi )
done
10 changes: 10 additions & 0 deletions npm/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The repo root .gitignore blanket-ignores *.mjs, *.cjs, and package.json to keep
# the Rust workspace JS-free. The npm shim under npm/ legitimately needs all three,
# so re-include them here (scoped to this directory).
!*.mjs
!*.cjs
!package.json

# ...but never commit build output or installed deps.
dist-npm/
node_modules/
42 changes: 42 additions & 0 deletions npm/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# newt-agent npm packages

Sources for newt-agent's npm distribution — Rust binaries delivered to npm users
the way `uv`, `esbuild`, and `@biomejs/biome` do (no Rust toolchain, no `pip`),
ported from the [scrybe](https://github.com/hartsock/scrybe) reference shim
(newt-agent#1221).

| Path | Package | Role |
|---|---|---|
| `newt-agent/` | [`newt-agent`](https://www.npmjs.com/package/newt-agent) | Unscoped umbrella — `npm i -g newt-agent` → the `newt` CLI. Depends on `@gilamonster/newt`. |
| `newt/` | `@gilamonster/newt` | `newt` CLI bin shim. Lists per-platform binaries as `optionalDependencies`; execs whichever npm resolved. |
| `newt-mcp-server/` | `@gilamonster/newt-mcp-server` | `newt-mcp-server` bin shim (same pattern). `npm i -g @gilamonster/newt-mcp-server`. |
| *(generated)* | `@gilamonster/<bin>-<os>-<arch>` | Per-platform packages carrying just the prebuilt binary + `os`/`cpu` fields. Built by the release job. |

## Design

- **Name-derived resolver.** `newt/lib/binary.cjs` and `bin/run.cjs` are **generic** —
each shim derives its binary name and platform-package names from its *own*
package name, so the exact same two files are shipped by every `@gilamonster/<bin>`
shim. Add a binary = one `package.json` (+ a copy of `bin/`, `lib/`, `platforms.json`).
- **No `postinstall`.** The binary arrives as a normal optional dependency —
hermetic, offline-cacheable.
- **`platforms.json`** is the single source of truth (`darwin-arm64`, `darwin-x64`,
`linux-x64`, `win32-x64`); `sync-versions.mjs` stamps exact pins at release.

## Scope (this PR)

Ships **`newt` + `newt-mcp-server`** — the two binaries `release.yml`'s
`build-binaries` actually produces (`-p newt-agent -p newt-mcp-server`). The
`@gilamonster/newt-mcp-data` and `@gilamonster/newt-provider-openai` names are
reserved but not shipped here — wire them in once those binaries are added to the
release build.

## Develop

```bash
cd npm && npm test # node --test: manifest integrity + resolver + happy-path exec
```

Publishing uses npm **OIDC trusted publishing** (see `.github/workflows/release.yml`
`build-npm` + `publish-npm-meta`). Each package has a trusted publisher configured
on npmjs pointing at `Gilamonster-Foundation/newt-agent` → `release.yml`.
19 changes: 19 additions & 0 deletions npm/newt-agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# newt-agent

Umbrella installer for [newt-agent](https://github.com/Gilamonster-Foundation/newt-agent) (Gilamonster).

```bash
npm install -g newt-agent
newt --help
```

The friendly single-name install. Depends on
[`@gilamonster/newt`](https://www.npmjs.com/package/@gilamonster/newt), which delivers
the prebuilt `newt` Rust binary for your platform via per-platform
`optionalDependencies` (the `uv` / `esbuild` pattern — no postinstall).

The MCP server is a separate install: `npm i -g @gilamonster/newt-mcp-server`.

Other channels: `cargo install newt-agent` · `pip install newt-agent`.

License: Apache-2.0
6 changes: 6 additions & 0 deletions npm/newt-agent/bin/run.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node
'use strict';

// Umbrella launcher. `newt-agent` depends on `@gilamonster/newt`; delegate to its
// launcher, which resolves and execs the platform binary (and exits).
require('@gilamonster/newt/bin/run.cjs');
18 changes: 18 additions & 0 deletions npm/newt-agent/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "newt-agent",
"version": "0.0.0",
"description": "Umbrella installer for newt-agent (Gilamonster). Installs the `newt` CLI binary for your platform; no Rust toolchain or pip required.",
"keywords": ["newt", "newt-agent", "gilamonster", "cli", "agent", "mcp"],
"homepage": "https://github.com/Gilamonster-Foundation/newt-agent#readme",
"bugs": "https://github.com/Gilamonster-Foundation/newt-agent/issues",
"repository": { "type": "git", "url": "git+https://github.com/Gilamonster-Foundation/newt-agent.git", "directory": "npm/newt-agent" },
"license": "Apache-2.0",
"author": "Shawn Hartsock",
"type": "commonjs",
"bin": { "newt": "bin/run.cjs" },
"files": ["bin/", "README.md"],
"engines": { "node": ">=18" },
"dependencies": {
"@gilamonster/newt": "0.0.0"
}
}
14 changes: 14 additions & 0 deletions npm/newt-mcp-server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# @gilamonster/newt-mcp-server

The `newt-mcp-server` binary ([newt-agent](https://github.com/Gilamonster-Foundation/newt-agent)),
delivered as a prebuilt Rust binary for your platform.

```bash
npm install -g @gilamonster/newt-mcp-server
newt-mcp-server --help
```

Same per-platform `optionalDependencies` pattern as `@gilamonster/newt` (no postinstall).
Not your platform? Build from source: https://github.com/Gilamonster-Foundation/newt-agent

License: Apache-2.0
31 changes: 31 additions & 0 deletions npm/newt-mcp-server/bin/run.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node
'use strict';

// Generic launcher shared by every @gilamonster/<bin> shim: resolve the platform
// binary and exec it, passing through argv, stdio, exit code, and signals.

const os = require('os');
const { spawnSync } = require('child_process');
const { binaryPath, BINARY } = require('../lib/binary.cjs');

let bin;
try {
bin = binaryPath();
} catch (err) {
process.stderr.write(`${err && err.message ? err.message : err}\n`);
process.exit(1);
}

const result = spawnSync(bin, process.argv.slice(2), { stdio: 'inherit' });

if (result.error) {
process.stderr.write(`${BINARY}: failed to launch ${bin}: ${result.error.message}\n`);
process.exit(1);
}

if (result.signal) {
const num = os.constants.signals[result.signal];
process.exit(num ? 128 + num : 1);
}

process.exit(result.status === null ? 1 : result.status);
62 changes: 62 additions & 0 deletions npm/newt-mcp-server/lib/binary.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
'use strict';

// Generic resolver for a `@gilamonster/<bin>` shim. Everything is derived from
// this package's OWN name, so the exact same file is shipped verbatim by every
// binary shim (newt, newt-mcp-server, …):
//
// @gilamonster/newt -> binary "newt", platform pkgs @gilamonster/newt-<platform>
// @gilamonster/newt-mcp-server -> binary "newt-mcp-server", @gilamonster/newt-mcp-server-<platform>
//
// The uv / esbuild optionalDependencies pattern — no postinstall, no network.

const fs = require('fs');
const path = require('path');

const self = require('../package.json');
const SHIM = self.name; // e.g. "@gilamonster/newt-mcp-server"
const BINARY = SHIM.split('/').pop(); // "newt-mcp-server"
const PLATFORMS = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'platforms.json'), 'utf8'));

const REPO = 'https://github.com/Gilamonster-Foundation/newt-agent';

function platformKey() {
return `${process.platform}-${process.arch}`;
}

function entryForCurrentPlatform() {
const key = platformKey();
return PLATFORMS.find((p) => p.key === key) || null;
}

function binaryFile(entry) {
return entry.os === 'win32' ? `${BINARY}.exe` : BINARY;
}

function binaryPath() {
const key = platformKey();
const entry = entryForCurrentPlatform();

if (!entry) {
const supported = PLATFORMS.map((p) => p.key).join(', ');
throw new Error(
`${BINARY}: no prebuilt binary for this platform (${key}).\n` +
`Supported: ${supported}.\n` +
`Install from source instead: ${REPO}`
);
}

const pkg = `${SHIM}-${entry.key}`;
try {
const pkgJsonPath = require.resolve(`${pkg}/package.json`);
return path.join(path.dirname(pkgJsonPath), binaryFile(entry));
} catch (_err) {
throw new Error(
`${BINARY}: the platform package "${pkg}" is not installed.\n` +
`This usually means optionalDependencies were skipped during install.\n` +
`Try: npm install -g newt-agent --include=optional\n` +
`Or install from source: ${REPO}`
);
}
}

module.exports = { PLATFORMS, BINARY, SHIM, platformKey, entryForCurrentPlatform, binaryPath };
21 changes: 21 additions & 0 deletions npm/newt-mcp-server/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"name": "@gilamonster/newt-mcp-server",
"version": "0.0.0",
"description": "newt-mcp-server — the newt-agent MCP server binary. Delivers the prebuilt Rust binary for your platform; no Rust toolchain or pip required.",
"keywords": ["newt", "newt-agent", "gilamonster", "mcp", "mcp-server"],
"homepage": "https://github.com/Gilamonster-Foundation/newt-agent#readme",
"bugs": "https://github.com/Gilamonster-Foundation/newt-agent/issues",
"repository": { "type": "git", "url": "git+https://github.com/Gilamonster-Foundation/newt-agent.git", "directory": "npm/newt-mcp-server" },
"license": "Apache-2.0",
"author": "Shawn Hartsock",
"type": "commonjs",
"bin": { "newt-mcp-server": "bin/run.cjs" },
"files": ["bin/", "lib/", "platforms.json", "README.md"],
"engines": { "node": ">=18" },
"publishConfig": { "access": "public" },
"optionalDependencies": {
"@gilamonster/newt-mcp-server-darwin-arm64": "0.0.0",
"@gilamonster/newt-mcp-server-linux-x64": "0.0.0",
"@gilamonster/newt-mcp-server-win32-x64": "0.0.0"
}
}
5 changes: 5 additions & 0 deletions npm/newt-mcp-server/platforms.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{ "key": "darwin-arm64", "os": "darwin", "cpu": "arm64", "rustTarget": "aarch64-apple-darwin" },
{ "key": "linux-x64", "os": "linux", "cpu": "x64", "libc": "glibc", "rustTarget": "x86_64-unknown-linux-gnu" },
{ "key": "win32-x64", "os": "win32", "cpu": "x64", "rustTarget": "x86_64-pc-windows-msvc" }
]
17 changes: 17 additions & 0 deletions npm/newt/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# @gilamonster/newt

The `newt` CLI ([newt-agent](https://github.com/Gilamonster-Foundation/newt-agent)),
delivered as a prebuilt Rust binary for your platform — no Rust toolchain, no pip.

```bash
npm install -g newt-agent # umbrella (recommended)
# or the scoped package directly:
npm install -g @gilamonster/newt
newt --help
```

Ships no binary itself: it declares per-platform `@gilamonster/newt-<os>-<arch>`
packages as `optionalDependencies`; your package manager installs the matching one
and the `newt` shim execs it. No `postinstall`. Not your platform? `cargo install newt-agent`.

License: Apache-2.0
Loading
Loading