Fixes #640. ```json "engines": { "node": ">=20.12.0" } ``` ## Why 20.12.0 and not 20 My first answer was `>=20` — CI's lowest tested line, satisfied by every runtime dependency, and the version the issue reporter (and I) installed successfully. **It is wrong**, and shipping it would have told npm it is safe to install onto runtimes where the CLI cannot execute a single line. The mistake was looking at first-party `src/`. The published artifacts are four esbuild bundles with their dependencies **inlined**, and that is where the real floor lives. Confirmed in `packages/argent/dist/`: | blocker | added in | consequence below it | |---|---|---| | `import { styleText } from "node:util"` (via inlined `@clack/*`, in `installer.mjs` and `cli-cmds.mjs`) | **20.12.0** | a **link-time** `SyntaxError` — `argent init` and every CLI subcommand fail to load | | `import.meta.dirname` at module scope (`bundled-paths.js`, `cli.js`) | 20.11.0 | `TypeError` during module init | So 20.12.0 is the lowest version on which the package actually loads. It is also the lowest line CI exercises (7 workflows pin Node 20), and it satisfies every runtime dependency's own constraint (`@fails-components/webtransport` `>=20`, `@modelcontextprotocol/sdk` `>=18`). `electron`'s `>=22.12.0` does **not** apply — it is an `optionalDependency`, and npm does not engine-check those. Verified: installing `0.18.0` on Node 20.19.5 produces no `EBADENGINE` at all and electron is never materialized. I did not go to `>=22` on EOL grounds: it would refuse a runtime seven CI workflows still test and that users demonstrably run. ## Docs corrected alongside - `README.md` said **20.11** — one minor below where the package can load. - `CONTRIBUTING.md` said **Node.js 18+** — predates what the repo's own lint toolchain needs (`eslint@10` requires `^20.19.0 || ^22.13.0 || >=24`). `packages/argent/README.md` is generated by `scripts/sync-readme.cjs` on `prepack`, so it follows automatically and is deliberately untouched. ## What a reviewer should know **Enforcement is package-manager-dependent**, which matters because `argent init` installs with whichever PM the project uses: | | behaviour on too-old Node | |---|---| | npm (default) | `npm warn EBADENGINE` — installs anyway | | npm + `engine-strict=true` | hard error, install aborts | | **yarn 1.x** | **hard error by default** | | **pnpm** | **silently ignores it** | So this achieves the issue's "warn" but only conditionally its "refuse". There is no `.npmrc` in the repo, so contributors are unaffected either way. **Nothing in the repo validates `engines`** — `repo-hygiene`, `check-workspace-versions.mjs` and `pack-mcp.cjs` all ignore unknown manifest keys, and `next-canary-version.mjs` round-trips the manifest through `JSON.parse`/`stringify`, which preserves the new field and stays prettier-clean. **One inconsistency I did not resolve:** `packages/argent/scripts/bundle-tools.cjs:383` builds with `target: "node22"`, which now sits above the declared floor. In practice the node20→node22 syntax gap is negligible (tsc targets ES2022 and I found no post-20 *syntax*), so I left the shipped artifact alone rather than change build output in a manifest-only PR — but it should either be lowered to match or consciously kept. Your call. ## Separate bug found while verifying this `ios-remote` is broken on the **entire Node 20 line**, independent of this change: the inlined `@moq/*` client uses `Promise.withResolvers` (Node 22+) ungated — 6 occurrences in `dist/tool-server.cjs`, reached from `utils/moq-client.ts` via the simulator-server blueprint. Reproduced on Node 20.19.5: `TypeError: Promise.withResolvers is not a function`. I deliberately did **not** raise the floor to `>=22` to paper over it — that would refuse Node 20 for every user to hide one broken path. `moq-client.ts` already has an `ensurePolyfill()` that polyfills `WebSocket`/`WebTransport`; this belongs there. Filed separately. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
7.3 KiB
Contributing to Argent
Thank you for your interest in contributing to Argent! This guide covers everything you need to get started.
Table of contents
- Requirements
- Setting up the dev environment
- Project structure
- Building
- Running the project
- Running tests
- Code style
- Submitting a pull request
Requirements
- macOS with Xcode installed (required for
xcrun simctland iOS simulator support) - Node.js 20.19+ (the lint toolchain's floor; the published package needs 20.12+)
- The
simulator-serverandax-servicebinaries inpackages/native-devtools-ios/bin/(arm64 macOS, installed separately vianpx @swmansion/argent install)
Setting up the dev environment
-
Fork and clone the repository:
git clone https://github.com/software-mansion/argent.git cd argent -
Install dependencies (npm workspaces installs all packages at once):
npm install -
Start the dev environment:
npm run devThis builds the native devtools dylibs (if the private submodule is available, otherwise uses pre-built binaries), compiles the MCP TypeScript, patches
~/.claude.jsonto point at the local MCP, and starts the tool-server from source viats-node. PressCtrl+Cto stop — the script automatically restores your global Argent configuration.To use a different port:
PORT=4000 npm run devNote:
packages/argent-privateis a private git submodule that holds the ObjC source for the native devtools dylibs. If you don't have SSH access to it,npm run devwill use the pre-built dylibs committed to the repository — everything else works normally.
That's it — no separate install steps per package are needed.
Project structure
This is an npm workspaces monorepo. All packages live under packages/:
| Package | Path | Purpose |
|---|---|---|
@argent/registry |
packages/registry |
Core library: dependency-aware service lifecycle, blueprints, tools, URNs |
@argent/tool-server |
packages/tool-server |
HTTP API over the registry (port 3001). Registers all blueprints and tools |
@swmansion/argent |
packages/argent |
Published bin shell — minimal dispatcher + bundled subcommands + native runtime assets |
@argent/tools-client |
packages/argent-tools-client |
HTTP client + tool-server launcher shared by @argent/mcp and @argent/cli |
@argent/mcp |
packages/argent-mcp |
MCP stdio protocol adapter — proxies Claude/Cursor calls to the tool-server |
@argent/cli |
packages/argent-cli |
Shell CLI commands: argent run, argent tools, argent server, argent enable, argent disable, argent flags |
@argent/installer |
packages/argent-installer |
Workspace setup logic: argent init, update, uninstall |
@argent/skills |
packages/skills |
Markdown skill files (prefixed argent-*) that instruct AI agents how to use Argent tools |
@argent/native-devtools-ios |
packages/native-devtools-ios |
Pre-built dylibs for iOS simulator injection (view hierarchy, network inspection). ObjC source lives in packages/argent-private (private submodule) |
The tsconfig.json at the root uses TypeScript project references; tsconfig.base.json holds shared compiler options (strict, ES2022, etc.).
Building
Local development (recommended)
npm run dev
Builds everything and starts the tool-server from source. See Setting up the dev environment for details.
Full build
Build all packages at once using TypeScript project references:
npm run build
To build a specific package:
npm run build -w @argent/registry
npm run build -w @argent/tool-server
To build and bundle the distributable MCP package:
npm run build -w @swmansion/argent
# or, to also produce a .tgz tarball:
npm run pack:mcp
Running the project
For day-to-day development, use npm run dev (see Setting up the dev environment).
Production-like start (builds bundles first, then starts from compiled output):
npm run start
This builds the registry, then starts the tools server on port 3001.
Verify the tools server is up:
curl http://localhost:3001/tools
Running tests
Tests are written with Vitest. Each package has its own test suite.
Run tests for a specific package:
npm test -w @argent/registry
npm test -w @argent/tool-server
Run tests in watch mode during development:
npm run test:watch -w @argent/registry
npm run test:watch -w @argent/tool-server
Code style
- TypeScript strict mode is enabled across all packages (
"strict": trueintsconfig.base.json). All code must compile without errors. - Target: ES2022 with CommonJS modules (except
@swmansion/argentwhich uses ESM). - Prefer explicit types over
any. Use Zod schemas for runtime validation where the codebase already does so. - Keep commits focused. Prefix commit messages with a type:
feat:,fix:,chore:,docs:,refactor:,test:. This feeds the auto-generated changelog on release.
Submitting a pull request
- Create a branch from
mainwith a descriptive name (e.g.feat/add-screenshot-tool,fix/session-leak). - Make your changes. Keep the scope of a PR small and focused — it's easier to review.
- Ensure the build passes:
npm run build - Ensure tests pass for the packages you touched.
- Write a clear PR title — it becomes part of the release changelog. Use the same prefix convention as commit messages (
feat:,fix:, etc.). - Open the PR against
mainand fill in the description with context on what changed and why. - A maintainer will review and may request changes. Address feedback with new commits (don't force-push after review starts).
Questions?
If you're unsure about something, open a GitHub Discussion or leave a comment on the relevant issue before spending time on a large change.