
# Programmatic API

> The `jup` package exports `runMain`. Use it to run the jup CLI from JavaScript.

## Run the CLI

Call `runMain(argv, options)` to run the same code as the `jup` command. It
returns an object with an exit code: `{ code }`.

By default, `runMain` does not call `process.exit()` or take control of your
process. You can call it from any part of a script, and you can call it more
than once.

Pass the command-line arguments as an array. Do not include `jup` itself. This
example runs the pnpm version chosen by the project:

```js
import { runMain } from "jup";

const { code } = await runMain(["pnpm", "install"]);
process.exitCode = code;
// Same as: jup pnpm install
```

You can include a version in the tool name. This example runs a Node.js version
that matches `^22`, without using a shim:

```js
import { runMain } from "jup";

const { code } = await runMain(["node@^22", "script.js", "--verbose"]);
process.exitCode = code;
// Same as: jup node@^22 script.js --verbose
```

You can also run jup commands:

```js
const { code } = await runMain(["info", "--json"]);
// Same as: jup info --json
```

Arguments, input, output, errors, exit codes, and signals work like they do in
the CLI. See the [command reference](./commands).

By default, jup starts each tool in a child process. It does not change your
`process.argv` or `process.execArgv`. Apart from the project environment file
described below, it does not change `process.env`. The returned `code` is the
tool's exit code. If signal N kills the tool, `code` is `128 + N`. Your script
keeps running.

```js
const install = await runMain(["pnpm", "install"]);
if (install.code !== 0) console.error("install failed");

// This is still your script, so you can run another command.
await runMain(["pnpm", "test"]);
```

There is one environment detail to know. For each run, jup reads the nearest
project environment file. It uses `.jup.env`, or `.corepack.env` when there is
no `.jup.env`. It copies allowed values from that file into `process.env`.
Existing environment variables take priority. See
[Settings](./settings#project-env-files) for the list of allowed values.

These copied values stay in `process.env`. In a long-running process, values
loaded for the first project may still be present when you run jup in another
project.

jup handles bad user input in the same way as the CLI. For example, an invalid
specification, an unknown tool, or a version blocked by the project returns
`{ code: 1 }`. jup writes the same message to stdout or stderr as the CLI. It
throws only when an operation fails or jup has a bug.

### Let the tool take over the process

Set `handover: true` only when the jup run is the last thing your script needs
to do. For a JavaScript tool, jup then runs the tool in the current process
instead of starting a child process. The `jup` command and its shims use this
mode. It lets a command such as `pnpm` run as one process instead of two.

```js
const { code } = await runMain(["pnpm", "install"], { handover: true });
if (code !== 0) process.exitCode = code;
```

Do not use this option in the middle of a script. The tool takes control of the
process. `runMain` returns `0` before the tool runs, and the tool sets the real
exit code. If a signal kills the tool, it also kills your process.

Use this option when you are writing a small CLI wrapper and have no more work
to do after the call.

## Other APIs

There are no other public entry points. Version selection, project discovery,
specification parsing, and the store are internal. The package does not export
any types. TypeScript still checks a call, because the option and result types
come with the `runMain` signature.

You can do everything jup supports through `runMain`. For project information,
`jup info --json` usually provides what a script needs.

If the CLI cannot do what your script needs, open an issue and describe what you
are trying to build. Keeping the public API small makes it easier to add new
features without breaking existing scripts.
