vx init
vx init sets up vx in a repository: it installs @vzn/vx, writes
.vx/workspace.ts and a root vx.config.ts, and writes the lock. In a
Turborepo or Nx repo it maps their config instead.
vx init [--dry [--format pretty|json]] [--force] [--mjs] [--native|--keep] [--plugin <seam>]Example
Section titled “Example”$ vx init --dry
─ vx init 0.0.0 ────────────────────────────────────────────── from no turbo.json or nx.json: one project, the root dry run nothing written files .vx/workspace.ts vx.config.ts .vx/.gitignore .vx/.gitattributes
next declare a task in vx.config.ts, then vx lock && vx run <task>Options
Section titled “Options”| Option | Meaning |
|---|---|
--dry |
print the files; write nothing |
--format json |
with --dry, the plan as one document |
--force |
replace a file already there |
--mjs |
write .mjs files instead of .ts |
--native, --keep |
in a Turbo or Nx repo: a vx.config.ts per package, or keep their config |
--plugin <seam> |
write a plugin for that seam instead, below |
Install
Section titled “Install”vx init first installs @vzn/vx globally (the package manager that
ran it, else npm; skipped when the vx on PATH is already that
version) and, under the topmost package.json, as a root
devDependency with that repo’s own manager (M, 2026-10-20). A failed
install stops init.
In a Turborepo or Nx repo
Section titled “In a Turborepo or Nx repo”When the directory it runs in, or one above it up
to the git top-level, holds a turbo.json, turbo.jsonc or nx.json,
it runs @vzn/vx-migrate there, so vx init is the one command
(owner, 2026-10-08): the latest release through bun x,
never an installed one, so init always installs the latest @vzn/vx
and moves every @vzn/vx-* plugin the root lists to it (M,
2026-10-16; a source checkout runs the nearest installed
node_modules/@vzn/vx-migrate instead) (BUN_BE_BUN=1 makes the compiled
binary that runtime). --dry, --format, --force, --mjs,
--native and --keep pass through, and the exit is vx-migrate’s.
vx-migrate maps the Turbo or Nx repo, one vx.config.ts per package; writes the workspace file with
the plugins the repo calls for (the lockfile’s, a remote cache the repo
shows); installs them; and ends with vx lock. Its README holds the
rules; bunx @vzn/vx-migrate below sums them up.
In any other repo
Section titled “In any other repo”With none of those files, it writes a workspace whose one project is
the root, at the nearest workspace root up from the working directory,
else at the topmost package.json, else there:
import { defineWorkspace } from '@vzn/vx'
export default defineWorkspace({ plugins: [] })import { defineProject } from '@vzn/vx'
export default defineProject({ tasks: {} })Beside a lockfile at that package.json (pnpm-lock.yaml, bun.lock,
yarn.lock, package-lock.json), the workspace declares its
@vzn/vx-lockfile plugin (plugins: [bun()]), installed beside
@vzn/vx, so a package’s ^build has project edges.
plus .vx/.gitignore and .vx/.gitattributes. It then runs vx lock’s
step, and next: says to declare a task and run vx lock && vx run <task>, preceded by git init outside a git work tree. The @vzn/vx
import needs no install: the running vx serves its own core to that
specifier (bin.ts registers the alias). A file already there is kept
and named under kept; --force replaces it, a file of another
extension included (replaced). --mjs writes .vx/workspace.mjs and
vx.config.mjs. --dry prints the files and writes nothing;
--dry --format json prints the plan as one JSON document
(schemas/init.json): files (each path and its whole text), kept,
replaced, todos, notes, next and installs (the install
commands the real run runs). --native and --keep answer
a Turbo or Nx repo’s question, so they are refused here. --format json
without --dry, or with --plugin, is refused (VX_E_USAGE).
--plugin
Section titled “--plugin”vx init --plugin <seam> writes a plugin instead: plugins/<seam>.ts,
a small runnable plugin for that seam (executor, cache,
telemetry, schedule, admit, commands, project, graph,
key), and plugins/<seam>.test.ts, which drives it through run()
(bun test, with @vzn/vx installed). It prints the line that
declares it in the workspace file a run reads (.vx/workspace.ts when
there is none). The templates are TypeScript, so --mjs with
--plugin is refused. An existing file is refused without
--force; --dry names the files and writes nothing. The two files are
private/vx-plugin-examples/plugins, which the gate runs, copied into
core (src/cli/plugin-templates.ts) and held equal by
tests/plugin-templates.unsafe.test.ts (H-21).
Exit codes
Section titled “Exit codes”| Code | When |
|---|---|
0 |
the files written (or, with --dry, printed) |
1 |
a refused flag, a parse error (--mjs with --plugin among them), or a plugin file already there without --force. Handed to @vzn/vx-migrate, the exit is its own |
bunx @vzn/vx-migrate
Section titled “bunx @vzn/vx-migrate”Mapping a repo’s own config to vx is @vzn/vx-migrate, its own
package, run before a workspace file exists (it writes one); vx init
runs it in any repo with a package.json —
bunx @vzn/vx-migrate # turbo.json, an Nx graph, vite-plus run.tasks, else package.json scripts → vx.config.ts (vx init hands it only Turbo and Nx)bunx @vzn/vx-migrate --dry # print the generated files instead of writingbunx @vzn/vx-migrate --force # overwrite existing vx.config.* / vx-preset.tsbunx @vzn/vx-migrate --from nx # disambiguate when several sources are checked in (turbo, nx, vite-task, scripts)— In a Turbo or Nx repo it asks a terminal native (a vx.config.ts
per package, the default) or keep (a workspace file declaring turbo()
or nx(), which read the repo’s config live); --native or --keep
answer it, and without a terminal it is native. Both declare
turboCache() / nxCache() when the repo shows a remote cache
(turbo.json’s remoteCache, a turbo linked .turbo/config.json, or
a CI file setting TURBO_TOKEN / NX_SELF_HOSTED_REMOTE_CACHE_SERVER),
and name Nx Cloud, whose wire vx does not speak. Elsewhere each
package’s package.json scripts become its tasks, and the workspace
file declares the @vzn/vx-lockfile plugin for the root’s lockfile.
What the package writes goes through core’s migration seam
(applyMigration, exported from @vzn/vx), which renders, guards
against overwriting, writes and reports. The mapping rules live in the
package’s README.