# AfterPack CLI reference

Invoking npx afterpack@latest: an optional path, every config key as a flag, the verify, restore and audit subcommands, and the full exit-code contract.

Source: https://www.afterpack.dev/docs/cli

`npx afterpack@latest` obfuscates a build directory. Use it when there is no plugin for your bundler, or in [CI](https://www.afterpack.dev/docs/builds). If a plugin exists for your framework, install `@afterpack/<fw>` instead: your normal build then emits obfuscated output with no separate step. See [Frameworks](https://www.afterpack.dev/docs/frameworks).

This page covers the [argument](#usage), [examples](#examples), [what a run does](#what-a-run-does), [`verify`](#afterpack-verify-dir), [`restore`](#afterpack-restore-dir), [`audit`](#afterpack-audit-url), [machine-readable output](#machine-readable-output), and the [exit codes](#exit-codes). Every option is defined once, on [Configuration](https://www.afterpack.dev/docs/config).

## Usage

```
afterpack [path] [--<key>=<value> ...] [-h | --help] [-v | --version]
afterpack verify [dir]
afterpack restore [dir]
afterpack audit <url>
```

```bash
npx afterpack@latest
```

The path takes either:

- a directory, walked recursively for every `.js`, `.mjs` and `.cjs`, or
- a single `.js`, `.mjs` or `.cjs` file, obfuscated on its own.

Run without a path and `afterpack` detects your build output: the directory a bundler named in your `package.json` conventionally writes (Next.js → `.next/`, Nuxt → `.output/`, SvelteKit → `build/`, Electron → `out/`, every other supported bundler → `dist/`) when that directory exists, else the newest of `dist/`, `build/`, `out/`, `.output/` and `.next/`. It never reads your bundler's own config file, so a custom output directory is not detected — pass the path yourself when you have one. It prints what it found and what it will do, then runs; in CI it never prompts. When nothing is found it prints the quickstart help and exits `1`. The command also reads your bundler from `package.json` and prints a one-line nudge toward the [matching plugin](https://www.afterpack.dev/docs/frameworks) at the end of a run.

Point it at your bundler's output, not your source. Most bundlers ([Vite](https://www.afterpack.dev/docs/frameworks/vite), [Rollup](https://www.afterpack.dev/docs/frameworks/rollup), [esbuild](https://www.afterpack.dev/docs/frameworks/esbuild), [webpack](https://www.afterpack.dev/docs/frameworks/webpack), [Astro](https://www.afterpack.dev/docs/frameworks/astro), [Parcel](https://www.afterpack.dev/docs/frameworks/parcel)) write to `dist/`. [Next.js](https://www.afterpack.dev/docs/frameworks/nextjs) writes to `.next/`, [Nuxt](https://www.afterpack.dev/docs/frameworks/nuxt) to `.output/`.

## Examples

```bash
npx afterpack@latest                                        # detects the build output, light default
npx afterpack@latest dist/                                  # a directory you choose
npx afterpack@latest dist/ --preset=hard                    # heavier posture
npx afterpack@latest .next/ --seed=git                      # reproducible output, pinned to the commit
npx afterpack@latest build/widget.js                        # one file
npx afterpack@latest dist/ --diagnostics.level=all          # every diagnostic, not the rolled-up summary
AFTERPACK_complexity=40 npx afterpack@latest dist/           # any flag also works as an env var
```

## What a run does

1. Collects the JavaScript at `<path>`. It recurses when the path is a directory, skips its own [`.backup.<hash>` copies](#files), and skips `node_modules/` unless you include it with [`--paths.include='**/node_modules/**'`](https://www.afterpack.dev/docs/config#paths-include). If nothing is collected, the build fails.
2. Keeps `.afterpack/` out of git with a `.gitignore` inside it, written the first time it is needed. Your own `.gitignore` is never edited.
3. Obfuscates every collected file in place, at the requested [preset](https://www.afterpack.dev/docs/config#preset), or at [`--complexity`](https://www.afterpack.dev/docs/config#complexity) when you set one explicitly. On a Next.js, Angular, Vite or webpack build it also renames each content-hashed file to match its obfuscated bytes and rewrites every reference to it; on other output the names stay as your bundler wrote them. See [Content-hashed file names](#content-hashed-file-names).
4. Writes the artifacts the flags allow, and always the [protection receipt](#files). The combined [Protection Map](https://www.afterpack.dev/docs/protection-map) always lands in `.afterpack/protectionMap.html`, gitignored and never served, because it embeds your original source. A source map or backup contains your full source. Never deploy either. If one lands under a served path segment, the CLI warns once.
5. Prints [diagnostics](https://www.afterpack.dev/docs/diagnostics), then the bundler nudge if it found one.

> **Runs are not idempotent**
>
> The command overwrites your build output in place and keeps no copy to re-obfuscate from. Build, then run it once. In [CI](https://www.afterpack.dev/docs/builds) that ordering is automatic.

### Do not run it twice on the same output

AfterPack refuses to obfuscate a file that already carries its own obfuscated output from a prior run: it matches the collected files against the [protection receipt](#afterpack-verify-dir) by content hash and fails the build with `DIAG_ALREADY_OBFUSCATED`, whatever directory you run it from, rather than obfuscating already-obfuscated code. A framework's copy of protected output, such as SvelteKit's `build/`, is refused the same way. Rebuild from source first — delete the output directory or run your bundler's clean step.

### Content-hashed file names

Bundlers name a chunk after a hash of its contents, such as `index-4f9a2c1e.js`, so a server can cache it for a year with `Cache-Control: immutable`. Obfuscation changes the bytes, so on the builds below the run gives each content-hashed file a new hash in its name and rewrites every reference to it in the HTML, manifests and other chunks. A name never points at different bytes, so a browser, CDN or service worker never serves a stale copy under a reused name. The new hash comes from the original one plus the seed, the engine version and your settings, the same way a [plugin](https://www.afterpack.dev/docs/frameworks) names its files; [File names and caching](https://www.afterpack.dev/docs/builds#file-names-and-caching) covers what the seed means for your cache.

| Build | Point the CLI at | What the run renames |
|---|---|---|
| [Next.js](https://www.afterpack.dev/docs/frameworks/nextjs), on webpack or Turbopack | `.next/`, `.next/static/` or `.next/static/chunks/`; `out/` for a static export | The chunks under `static/chunks/` |
| [Angular](https://www.afterpack.dev/docs/frameworks/angular) application builder | `dist/<app>/browser/` or `dist/<app>/` | Every hashed file. The run also recomputes the `integrity` attributes and the service worker's `ngsw.json` hash table |
| [Vite](https://www.afterpack.dev/docs/frameworks/vite) | A directory with an `index.html` or a `.vite/manifest.json` | The `name-<hash>` scripts and stylesheets in `assets/`, or in the folders the manifest names |
| [webpack](https://www.afterpack.dev/docs/frameworks/webpack) | A directory whose webpack runtime loads the chunks | The files with a hex content hash in their name |

Content-hashed names stay as your bundler wrote them, and the run prints one notice, when:

- the path is a single file, or the output comes from any other bundler;
- the bundler behind a Next.js build cannot be identified;
- on a Vite or webpack build, a hashed file is not referenced from inside the build directory, two hashes cannot be told apart, or a folder beside the build directory, such as a server bundle, `.svelte-kit/` or `.output/`, names the hashed files or is too large to check;
- a precache manifest, such as Workbox's, lists the files by revision.

For long-lived caching on such a build, use your framework's [plugin](https://www.afterpack.dev/docs/frameworks), or pin a constant [`--seed`](https://www.afterpack.dev/docs/config#seed).

An Angular server build whose `angular-app-manifest.mjs` embeds asset hashes is refused with exit `1`, and the output is left as the builder wrote it. Obfuscate the browser output of a build without SSR.

## Flags

Every configuration key is a flag: `--<key>=<value>`, for example `--complexity=40` or `--identifiers.reserved=Hls,Foo` for a list. A boolean flag is bare for `true` (`--build.backup`) and `=false` turns it off (`--protectionMap.enabled=false`). `-h, --help` and `-v, --version` print and exit `0`.

Every scalar or list key in the [option reference](https://www.afterpack.dev/docs/config) works as a flag this way. Structured values belong in `afterpack.json`. Environment variables follow the same names with dots turned to underscores, see [Configuration](https://www.afterpack.dev/docs/config), for example [`AFTERPACK_key`](https://www.afterpack.dev/docs/config#key), [`AFTERPACK_preset`](https://www.afterpack.dev/docs/config#preset) and [`AFTERPACK_telemetry_enabled`](https://www.afterpack.dev/docs/config#telemetry-enabled).

## Files

| Path | Role |
|---|---|
| `afterpack.json` | The config file. AfterPack walks up from the working directory to the nearest match. It holds the whole [engine config](https://www.afterpack.dev/docs/config#example-afterpackjson): build options, [`key`](https://www.afterpack.dev/docs/config#key), everything. |
| `.afterpack/receipts/<build dir>.json` | The protection receipt for one build directory, for example `.afterpack/receipts/dist.json` or `.afterpack/receipts/.next.json`. A long or unusual path gets a shortened name with a hash. It records every output file with the hash of the bytes written, the engine version and the seed. [`afterpack verify`](#afterpack-verify-dir) re-hashes against it, and a later run over the same unrebuilt output is refused because of it. |
| `.afterpack/protectionMap.html` | The combined [Protection Map](https://www.afterpack.dev/docs/protection-map). Gitignored automatically. It embeds your original source. Never commit or serve it. |
| `.afterpack/backups/<build dir>/` | Your originals for one build directory, verbatim, for example `.afterpack/backups/dist/`, with a manifest of every file the run created, changed, renamed or removed. Each build directory has its own backup and its own [lock](#parallel-runs-and-locks), so builds of different apps in one repository never share one. Written on every run ([`--build.backup=false`](https://www.afterpack.dev/docs/config#build-backup) opts out), and [`afterpack restore`](#afterpack-restore-dir) reads it to undo the run. The run prints its path when it finishes. It is your source. Never commit or deploy it. |
| `<file>.js.map` | The emitted source map, when [`sourceMap.enabled`](https://www.afterpack.dev/docs/config#sourceMap-enabled) is on. Off by default: a run is [production](https://www.afterpack.dev/docs/config#build-mode) unless it says development, and a production run also deletes the source maps your bundler wrote, CSS maps included. A shipped source map maps the output straight back to your source. |

`.afterpack/` holds the receipts, the Protection Map and the backups. AfterPack finds it from the build directory, whatever directory you run from. It walks up to the nearest folder that holds a lockfile (`package-lock.json`, `npm-shrinkwrap.json`, `pnpm-lock.yaml`, `yarn.lock`, `bun.lock` or `bun.lockb`), `afterpack.json` or `.git`. Failing that it uses the nearest folder with a `package.json`, and failing that it goes beside the build directory. That folder is your project root on these pages. `.afterpack/` is never inside the build output.

A run fails, with nothing rewritten, when it finds a `.afterpack/` folder inside the build output (`DIAG_AFTERPACK_DIR_IN_OUTPUT`: delete that folder and build again) or cannot write the receipt (`DIAG_RECEIPT_UNWRITABLE`). A `.afterpack-protection.json` an older version left in the output is removed on the next run.

## `afterpack verify [dir]`

The deploy gate. Every protected build writes a protection receipt to [`.afterpack/receipts/`](#files). `verify` finds the receipt for `[dir]` the same way the run did, so it works from any directory. It re-hashes every file the receipt names and fails when one no longer matches, when the receipt is missing, or when it is from a different build.

```bash
npx afterpack@latest verify .        # a project root: looks in ./ then ./.next/
npx afterpack@latest verify dist     # or the build output directory itself
npx afterpack@latest verify build    # SvelteKit's adapter copy; also .output/public, out/
```

A directory a framework copied from its build output, such as SvelteKit's `build/`, Nuxt's `.output/public/` or Next's `out/`, is checked against the receipt of the directory it copied. `verify` matches the copy to that receipt by its files and checks every hash, so a changed file fails and is named. Point it at the deploy root itself. The text output then adds `· copy of <source dir>/`.

It reads no engine configuration — only [`--diagnostics.format`](#machine-readable-output) and `--diagnostics.level`. With `--diagnostics.format=json`, the summary's `receipt` field names the receipt it checked, and `copiedFrom` names the directory a copy was made from. Run it as the last step before deploy; see [Builds & CI](https://www.afterpack.dev/docs/builds).

It also fails when a stopped run or restore left a temporary `.afterpack-tmp` file in the build directory. See [A run that stopped partway](#a-run-that-stopped-partway).

## `afterpack restore [dir]`

The undo. Before a run writes anything, it copies your originals into `.afterpack/backups/<build dir>/` and records every file it will create, change, rename or remove. `restore` puts each file back under its original name with its original bytes, undoes the renames and the rewritten references, and deletes the backup and the receipt the run wrote.

```bash
npx afterpack@latest restore          # every backup under the project root
npx afterpack@latest restore dist     # only the run over dist/
```

Pointed at a build directory, it restores only the runs over that directory, a directory inside it, or the build that contains it. Pointed at the project root, it restores every backup there. When no backup covers the path, it fails and lists the directories that have one. It finds `.afterpack/` from `[dir]` the same way a run does, and also reads a `.afterpack/backup/` folder an earlier release wrote.

It fails closed: every recorded file is checked *before* anything is written, so a file you edited or rebuilt since the run stops the restore, nothing is restored, and the command names the file and exits `1`. With no backup it refuses outright. A restore that was stopped partway is finished by the next `restore`. It reads no engine configuration beyond [`--diagnostics.format`](#machine-readable-output).

### A run that stopped partway

A run that fails on an error rolls back its own writes before it exits. Every file is written to a temporary `<file>.afterpack-tmp` and then renamed into place, so a run that is killed never leaves a half-written file. A killed run's backup stays marked unfinished, and the next `afterpack` run over the same directory checks the output first:

| The build output holds | The next run |
|---|---|
| What your bundler wrote and what the stopped run wrote, nothing else | Fails with exit `1`. Run `afterpack restore` to put the output back as your bundler wrote it, or rebuild from source and run again. |
| A rebuild, or exactly what your bundler wrote, with nothing of the stopped run left | Discards the stale backup with a notice, then runs. |
| A mix: some files rebuilt, others still holding the stopped run's output | Fails with exit `1` and names the rebuilt files, and `restore` refuses too. Rebuild the whole output from source and run again, or copy the originals of the other files back from the backup folder the message names. |

With [`--build.backup=false`](https://www.afterpack.dev/docs/config#build-backup) no backup is written, so a killed run cannot be undone: rebuild from source. AfterPack does not flush its writes to disk, so after a power loss during a run or a restore, rebuild from source as well.

### Parallel runs and locks

A run and a restore each hold the lock of their build directory, `.afterpack/backups/<build dir>/lock`, until they finish. Runs over different build directories, such as two apps in one repository, go in parallel. A second run over the same build, including a directory inside it such as `.next/static/chunks/` while `.next/` is being protected, fails with exit `1` and names the run that holds the lock. A lock whose process on this machine has exited, or that is older than two hours, is taken over with a notice. If no run is going and the lock stays, delete the file the message names.

## `afterpack audit <url>`

Scans a **deployed** site for leaked secrets, exposed source and unprotected JavaScript, streaming the findings as the scan runs. It reads no configuration and sends nothing but the URL. See [Audit](https://www.afterpack.dev/docs/audit) for the full command reference.

```bash
npx afterpack@latest audit example.com
```

## Machine-readable output

`--diagnostics.format=json` puts exactly one JSON document on stdout and nothing else; every human-readable line moves to stderr. It works from the flag, `AFTERPACK_diagnostics_format=json`, or `diagnostics.format` in `afterpack.json`, and `verify` and `audit` honor it too. Pair it with `diagnostics.level=none` for a run that prints nothing but the document. This is the shape CI steps and AI agents should read instead of parsing text output; the package also ships a `SKILL.md` that documents the CLI for an agent invoking it directly.

```bash
npx afterpack@latest dist/ --diagnostics.format=json --diagnostics.level=none
```

## Exit codes

| Code | Meaning |
|---|---|
| `0` | Success. Every collected file was obfuscated and written. Also returned by [`--help` and `--version`](#usage). |
| `1` | Failure. Nothing usable was produced: a path that does not exist, no JavaScript found under it, any engine error, a failed `verify`, a failed `audit` scan, or a Pro-keyed build whose cloud call failed. |
| `2` | Partial. Reserved for a quota-exhausted build; no build returns it today. |
| `3` | Size cap. [`inflation.max`](https://www.afterpack.dev/docs/config#inflation-max) could not reach the complexity target (`DIAG_SIZE_CAP_REACHED`). |
| `4` | Pro required. A [`regions`](https://www.afterpack.dev/docs/config#regions) entry raises protection above the program's configuration and needs a [`key`](https://www.afterpack.dev/docs/config#key) (`DIAG_PRO_DIRECTIVE_REQUIRES_KEY`). The CLI reads no source directives, so only `regions` reaches this exit. |
| `5` | Reflection. A runtime-reflection pattern was detected without [`reflection.allow`](https://www.afterpack.dev/docs/config#reflection-allow) (`DIAG_REFLECTION_NOT_ACKNOWLEDGED`). |
| `6` | Update required. The AfterPack cloud, or this `afterpack`, needs a newer `@afterpack/core`. Nothing was written. |
| `64` | Misuse. An unknown flag or command, a malformed value, or a duplicated path argument. |

Use [`paths.exclude`](https://www.afterpack.dev/docs/config#paths-exclude) to carve out a file that must ship untouched.

A Pro build with a resolved [`key`](https://www.afterpack.dev/docs/config#key) fails closed. If the cloud call fails, the build stops instead of falling back to Free-tier output. `4` is a Free build's exit for a `regions` entry that needs a key; `5` is the exit for an unacknowledged reflection pattern on any build, Free or Pro; `2` is reserved for a future quota-exhausted build and no build returns it today. See [Exit codes](https://www.afterpack.dev/docs/diagnostics#exit-codes) for the full contract.

## Next

- [Configuration](https://www.afterpack.dev/docs/config): every option, and the four forms that can set it.
- [Presets](https://www.afterpack.dev/docs/presets): the five-rung ladder and what each rung costs.
- [Audit](https://www.afterpack.dev/docs/audit): the full `afterpack audit` reference.
- [Diagnostics](https://www.afterpack.dev/docs/diagnostics#exit-codes): the exit contract, the fail-closed rule, and every `DIAG_*` code.
- [Frameworks](https://www.afterpack.dev/docs/frameworks): the plugin path, better when a plugin exists for your stack.
- [Quickstart](https://www.afterpack.dev/docs/quickstart): the shortest route from install to a [Protection Map](https://www.afterpack.dev/docs/quickstart#check-the-result).
