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: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ A CLI (published as `@stackguardian/mcp-wizard`, command `add-sg-mcp`) that conn

- `src/sg/` — everything StackGuardian-specific: `preset.ts` (URLs, header, validation), `credentials.ts` (credential store), `auth.ts` (pieces the browser sign-in shares: state, callback page, URL opener), `oauth.ts` (grant sign-in through the OAuth broker), `skills.ts` (skills installer), `commands.ts` (Commander wiring for the default connect flow, `login`, `logout`, `status`, `remove`), `types.ts`.
- `src/agents.ts`, `src/installer.ts`, `src/formats/*`, `src/schema.ts`, `src/reader.ts`, `src/opencode-config.ts`, `src/source-parser.ts`, `src/template.ts` — upstream, kept byte-identical so `git merge upstream/main` stays cheap. Do not put StackGuardian logic there.
- `src/index.ts` — the bin. Upstream's `main()` install flow plus the registration of the `sg` commands (`registerSgCommands`, dependencies injected to avoid an import cycle).
- `src/cli.ts` — the npm bin: refuses Node.js older than `engines`, then loads `src/index.ts`. The standalone executable builds from `src/index.ts` directly, since it brings its own runtime.
- `src/index.ts` — the CLI. Upstream's `main()` install flow plus the registration of the `sg` commands (`registerSgCommands`, dependencies injected to avoid an import cycle).
- `skills/` — the bundled `SKILL.md` folders shipped in the npm package.
- `install.sh`, `install.ps1` — installers for the standalone executables that `.github/workflows/binaries.yml` builds (`bun run build:binary`), uploads to `dl.stackguardian.io` and attaches to each GitHub release.
- `docs/superpowers/specs/` and `docs/superpowers/plans/` — design and implementation plan.
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

## 1.3.1

- Node.js older than 18 now gets a clear error as soon as the tool starts, with the version you are running and the commands to install the standalone executable, which does not need Node.js. Before, npm printed an easy-to-miss engine warning and the tool carried on: the browser sign-in then failed with `fetch is not defined`, after you had already approved access. This also stops `--token` installs on those versions, which happened to work but were never supported. Node.js 18 and 20 keep working.

## 1.3.0

- The connect flow and `login` now send usage events to StackGuardian when an install starts, when you sign in and when it completes, so we can see where installs stop. They are tied to a random ID on your machine and carry your organization and region, never the key or the grant token. Sending adds at most two seconds to a run. Set `DO_NOT_TRACK=1` or `SG_NO_TELEMETRY=1` to turn them off. See "Usage analytics" in the README.
Expand Down
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@stackguardian/mcp-wizard",
"version": "1.3.0",
"version": "1.3.1",
"description": "Connect your coding agents to StackGuardian: installs the StackGuardian MCP server and skills into Claude Code, Codex, Cursor, VS Code, Gemini CLI, OpenCode and more with a single command.",
"author": "StackGuardian <support@stackguardian.io>",
"license": "Apache-2.0",
Expand All @@ -15,7 +15,7 @@
"./package.json": "./package.json"
},
"bin": {
"add-sg-mcp": "dist/index.js"
"add-sg-mcp": "dist/cli.js"
},
"files": [
"dist",
Expand All @@ -24,7 +24,7 @@
],
"scripts": {
"fmt": "prettier --write .",
"build": "tsup src/index.ts src/lib.ts --format esm --dts --clean",
"build": "tsup src/cli.ts src/index.ts src/lib.ts --format esm --dts --clean",
"build:binary": "bun build src/index.ts skills/*/SKILL.md --compile --minify --loader .md:file --asset-naming '[dir]/[name].[ext]'",
"dev": "tsx src/index.ts",
"test": "node tests/run.mjs all",
Expand Down
17 changes: 17 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env node

// The npm bin. npm only warns about `engines`, so check the version here, before
// index.ts and its dependencies load: below Node 18 the sign-in fails on the missing
// global fetch, and only after the user has approved access in the browser.
import { nodeVersionError } from "./sg/node-version.js";

const versionError = nodeVersionError(process.versions.node);
if (versionError) {
console.error(versionError);
process.exit(1);
}

import("./index.js").catch((error: unknown) => {
console.error(error);
process.exit(1);
});
19 changes: 19 additions & 0 deletions src/sg/node-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/** Oldest Node.js major the npm package runs on; `engines` in package.json says the same. */
export const MIN_NODE_MAJOR = 18;

/** What to tell the user when `version` (`process.versions.node`) is too old, otherwise null. */
export function nodeVersionError(version: string): string | null {
if (Number.parseInt(version, 10) >= MIN_NODE_MAJOR) return null;
return [
`add-sg-mcp needs Node.js ${MIN_NODE_MAJOR} or later, and this is Node.js ${version}.`,
"Upgrade Node.js, or install the standalone executable, which does not need Node.js:",
"",
" # macOS and Linux",
" curl -fsSL https://dl.stackguardian.io/add-sg-mcp/install.sh | sh",
"",
" # Windows (PowerShell)",
" irm https://dl.stackguardian.io/add-sg-mcp/install.ps1 | iex",
"",
"More: https://github.com/StackGuardian/stackguardian-mcp#install",
].join("\n");
}
1 change: 1 addition & 0 deletions tests/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ const UNIT = [
"tests/sg-oauth.test.ts",
"tests/sg-skills.test.ts",
"tests/sg-telemetry.test.ts",
"tests/sg-node-version.test.ts",
];
const E2E = [
"tests/e2e/install.test.ts",
Expand Down
71 changes: 71 additions & 0 deletions tests/sg-node-version.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#!/usr/bin/env tsx
import assert from "node:assert";
import { readFileSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import { MIN_NODE_MAJOR, nodeVersionError } from "../src/sg/node-version.js";

let passed = 0;
let failed = 0;

function test(name: string, fn: () => void) {
try {
fn();
console.log(`✓ ${name}`);
passed++;
} catch (err) {
console.log(`✗ ${name}`);
console.error(` ${(err as Error).message}`);
failed++;
}
}

const packageJson = JSON.parse(
readFileSync(
join(dirname(fileURLToPath(import.meta.url)), "..", "package.json"),
"utf-8",
),
);

test("Node versions below the minimum get an error naming both versions", () => {
for (const version of ["14.21.3", "16.20.2", "17.9.1"]) {
const error = nodeVersionError(version);
assert.ok(error, `expected an error for ${version}`);
assert.ok(error.includes(version));
assert.ok(error.includes(`Node.js ${MIN_NODE_MAJOR} or later`));
}
});

test("the error gives the standalone install commands from the README", () => {
const error = nodeVersionError("16.20.2");
assert.ok(error);
const readme = readFileSync(
join(dirname(fileURLToPath(import.meta.url)), "..", "README.md"),
"utf-8",
);
for (const command of [
"curl -fsSL https://dl.stackguardian.io/add-sg-mcp/install.sh | sh",
"irm https://dl.stackguardian.io/add-sg-mcp/install.ps1 | iex",
]) {
assert.ok(error.includes(command), `error is missing: ${command}`);
assert.ok(readme.includes(command), `README is missing: ${command}`);
}
});

test("Node 18 and later pass, including the EOL 18 and 20", () => {
for (const version of ["18.0.0", "18.20.8", "20.20.2", "22.23.4", "26.0.0"]) {
assert.strictEqual(nodeVersionError(version), null);
}
});

test("the minimum matches engines in package.json", () => {
assert.strictEqual(packageJson.engines.node, `>=${MIN_NODE_MAJOR}`);
});

test("the npm bin is the entry that checks the version", () => {
assert.strictEqual(packageJson.bin["add-sg-mcp"], "dist/cli.js");
assert.ok(packageJson.scripts.build.includes("src/cli.ts"));
});

console.log(`\n${passed} passed, ${failed} failed`);
process.exit(failed > 0 ? 1 : 0);
Loading