@vodmal/vdx-cli
v0.44.0
Published
vdx — unified lifecycle interface (up/down/build/test/check/fix) + versioned maturity audit with drift detection. Pairs with the vdx Claude Code plugin.
Maintainers
Readme
@vodmal/vdx-cli — vdx evaluator + MCP server
Native predicate evaluator for vdx-rubric-vodmal + MCP stdio server for the
Claude Code vdx plugin. Decision basis: docs/decisions.md
D10 (native evaluator, not OPA).
Published on npm as @vodmal/vdx-cli.
Install
Two equally supported paths:
# A. Daily-use install — recommended when you run `vdx <verb>` many times per session
npm install -g @vodmal/vdx-cli
vdx audit /path/to/project
# B. Zero-install via npx — recommended for CI runners, one-shot trial, or fresh envs
npx -y -p @vodmal/vdx-cli vdx audit /path/to/projectSame binary, same behavior. npx adds ~200–500 ms resolve overhead per
invocation; pick global when you'll run vdx repeatedly, npx when you
don't want anything in your global node_modules or you're in an
ephemeral environment.
First command after install: vdx doctor — it inspects your environment
(Node version, vdx on PATH, git, mise, npm auth, container runtime, Claude
Code plugin; in a project — whether its git hooks are on in this clone, and the
commit author; with a profile — whether your personal hooks run on this machine)
and points to remedies for everything that's missing or sub-optimal.
Use
# Lifecycle verbs (pass-through to `mise run <verb>`; requires mise + mise.toml)
vdx up | down | build | test | check | fix
# Maturity audit against the owner baseline rubric
vdx audit [project-path] [--format=ansi|markdown|json] [--rubric <path>] [--stack <id>] # default: cwd
# Generate mise.toml for a project (no AGENTS.md — vdx-discover skill covers Claude Code; pick your own format for other agents)
vdx init [project-path] [--baseline github.com/org/[email protected]] [--stack <id>] [--dry-run] [--force] # default: cwd
# Publish a library (Node MVP; PHP/Python coming in Y.3)
vdx publish <patch|minor|major> [--dry-run] [--force] [--no-push]
# Push the branch; one that requires checks on GitHub takes the commit once they pass
vdx push [project-path] [--dry-run] # default: cwd
# Environment self-check (Node / git / mise / npm auth / docker / Claude Code plugin; git hooks of the project; the GitHub token vdx asks with)
vdx doctor [project-path] [--format=ansi|markdown|json] # default: cwd
vdx doctor --staged # pre-commit guard: Claude Code memory goes into a commit only where nobody but
# you reads every place it reaches (origin, mirrors; GitHub asked with gh's token)
vdx doctor --check [project-path] # one line per hook declared but not running here, and one when the memory is
# not inside — the plugin's SessionStart hook tells the agent
vdx doctor --fix [project-path] # write the profile's personal hooks (git.hooks) into ~/.gitconfig, and put
# the project's Claude Code memory inside it: .claude/memory/ (after a
# folder move, its path and conversations follow; notes a session that
# started before the move wrote at the old place come in)
# Start your agent in a project per your profile (~/.vdx-environment.yaml or $VDX_ENVIRONMENT)
vdx ai[@host] [project-path] [--new | --conversation <id>] [--focused [<topic>] | --intercom] [--restart] [--detach] [--dry-run | --list] # default: cwd; @host: over ssh, in tmux there; --intercom: the project's mail session
vdx ai [project-path] --focused "<topic>" # a focused session about <topic> (tmux option @vdx_topic); run by that session's agent: a new topic, nothing started
vdx ai --close [--waiting <n>] # inside the mail session: it ends itself; n questions to you still unanswered show on the project's other tmux sessions
vdx ai --check [project-path] # this session against the profile and the installed Claude Code and plugins — what the plugin's SessionStart hook tells the agent
# Move projects into a folder on every machine of the profile (session.machines), with what Claude Code keeps by
# the folder's path: memory path, conversations, trust; idle sessions are closed and started again there.
# Within, into, out of or beside Syncthing: its folders pause while it moves; copies Syncthing would merge — one goes
vdx move <project-path>... <folder> [--dry-run]
# What a command does and takes; nothing runs (ai: also where launch flags live)
vdx <command> --help
# Print the installed version
vdx --version
# MCP stdio server consumed by the Claude Code plugin
vdx-mcp --project <path>Three quick examples
# 1. Score the project I'm standing in (no arg → cwd)
vdx audit
# 2. Wire native scripts into the 6 lifecycle verbs, then run one
vdx init --stack node
vdx test # → mise run test (which calls `vitest run` or whatever was detected)
# 3. Ship a new minor release of a Node lib
vdx publish minor # npm login if expired → bump package.json + lock → npm publish (OTP or browser) → commit + tag → push → wait for the registry
# in your own terminal: without one it releases nothing (--force goes on)
# 4. Start the agent here, continuing its last conversation (--new: a new one,
# next to a running agent; another machine's conversation is continued on
# that machine);
# re-run to attach; an agent without the profile's flags is a question:
# Enter attaches as is, r restarts it; --restart applies a changed profile
vdx ai
# 5. The same on another machine (an ssh host with vdx), attaching from here
vdx ai@m3vdx push pushes the current branch to its upstream. When the upstream is on
GitHub and requires checks (branch protection or a ruleset), the commit goes to
vdx-gate/<branch> first; CI checks it there — its workflow has to run on
vdx-gate/** — and vdx waits for the checks, then pushes the same commit to the
branch with its annotated tags. A failed check leaves the branch as it was and
names the check. Without required checks, or off GitHub, it is a plain
git push. Its pushes through the checks carry VDX_GATE_PUSH=1, so a pre-push
hook that runs what CI runs can step aside. vdx publish sends its release
commit the same way, after npm publish, and then refuses local commits not
pushed yet: npm would get them before CI.
vdx ai reads a personal profile, never a bundled one: $VDX_ENVIRONMENT, else
~/.vdx-environment.yaml, else a plain claude/codex with no flags. Format:
docs/specs/environment-format.md.
The session's {project} is [_.vdx] name (or the older [vdx]) from the project's mise.toml, else the
first file in the profile's session.project_names that has one, else the repo
name. vdx ai@<host> runs the same command on <host> over ssh — that machine's vdx,
profile and tmux, the path taken under its home directory; --detach only
starts it, and not in a folder Claude Code does not trust yet: nobody would answer
its trust window (exit 4; start once without --detach). The machine's own label ($VDX_HOST) runs here. Where git knows no
commit author, vdx ai proposes one (Enter writes it into .git/config).
For Claude Code vdx ai picks the conversation: each one is marked with the
machine it was last started on (vdx ai --check, run by the plugin's hook, writes
the label in), the running ones are asked of the other machines over ssh, and
another machine's conversation is continued there. Unless the choice is plain
(0.44+: one agent on all the machines that no terminal at your seat shows; or
none running and this machine's newest conversation not finished) it shows one
list of the agents on every machine and the conversations nobody runs, with
their state; Enter never takes one your seat already shows; without a terminal,
this machine's. vdx ai --list prints the list and starts nothing.
Every command takes --help (-h) and runs nothing with it. An unknown option,
a stray argument or a value flag without its value is refused with exit 2
before anything runs. In 0.16.0 and earlier the commands other than vdx ai
dropped such arguments, and vdx init --help wrote mise.toml.
By default the bundled rubric/vdx-rubric.yaml is used (a mirror of canonical
vdx-rubric-vodmal at the time of
each CLI release). Override via --rubric <path> or the env var VDX_RUBRIC.
branch-protection is the one axis the files cannot answer: vdx audit asks
the hosting of origin whether its default branch takes changes only after a
check required of everyone, admins included. GitHub: protection or a ruleset
nobody bypasses; a public repo needs no token, a private one takes it from
GH_TOKEN, GITHUB_TOKEN or gh auth token. GitLab (a host with gitlab in
its name, or one glab has a token for): nobody may push to the branch directly,
and a merge request needs a passing pipeline; the token is glab's for that
host, or GITLAB_TOKEN when the host is GITLAB_HOST (gitlab.com unless
set) — vdx never sends a token to a host it does not belong to. Bitbucket
Server (a host BITBUCKET_HOST names, one with bitbucket in its name, or one
git keeps an https login for — and that says it is Bitbucket): a restriction
lets nothing into the branch but a pull request and exempts nobody, and a pull
request needs a successful build ("Minimum successful builds"); the login is
the one git keeps for that host (git credential fill, never a prompt — log in
once with git ls-remote https://<host>/scm/<project>/<repo>.git, a personal
access token as the password), or BITBUCKET_TOKEN when the host is
BITBUCKET_HOST. Branch restrictions are visible to a repository admin only.
A login the server refuses (a revoked token) is unknown with the command that
erases it: vdx leaves git's store alone, and sends the login again each audit
until it is gone.
Not met comes with the reason; another hosting, no network or no token
where one is needed leave the axis unknown, with the reason too;
VDX_OFFLINE=1 does not ask at all.
Dev
git clone https://github.com/VoDmAl/vdx
cd vdx/cli
npm install
npm run audit -- /path/to/project --json
npm run smoke # gauge against 3 calibration references
npm run typecheckStructure
src/rubric.ts— types + YAML loadersrc/manifest.ts— parser for the[_.vdx]block inmise.toml(the older[vdx]too) +.vdx-overrides.ymlsrc/facts.ts— fact source loaders (tasks, packages, configs, sub-package detection)src/predicates.ts— predicate registry; true / false / unknown with a reason (schema 0.3)src/evaluator.ts— recursive evaluator + sugar notationsrc/scoring.ts— delta-style levels,not_requiredlevels, flags for orthogonal axessrc/gha.ts— GitHub Actions workflows as theciaxis reads themsrc/vocabulary.ts— which project tasks a command calls through the project's runnersrc/hooks.ts— one recognizer of hook frameworks for audit and doctorsrc/clone-checks.ts— clone checks printed by doctor and next to an axis in auditsrc/personal-hooks.ts— the profile's personal hooks in ~/.gitconfig: check and--fixsrc/audit.ts— orchestrator: overrides + applies_to filter + subpackage-ctxsrc/init.ts—vdx initplanner (selectVerbTask+ mise.toml renderer)src/run.ts—resolveLifecycleVerb+ error renderer (pure logic forvdx <verb>)src/publish.ts—planPublish(pre-flight) +executePublish(bump → npm → git)src/ai.ts—vdx ai: profile loader, launch plan, tmux session handlingsrc/cmux.ts— the focused topic as the cmux workspace description whilevdx aiholds the attach (bin/vdx-cmux-topic.cjswatches it)src/move.ts—vdx move: checks on every machine (Syncthing folders and peers, copies, containers, processes), Syncthing paused for the mv on each, memory path, conversations and trust followsrc/report.ts— markdown / JSON outputsrc/index.ts— CLI entrysrc/mcp-server.ts— MCP stdio server (9 tools)src/defaults.ts— bundled rubric resolutionbin/vdx.cjs,bin/vdx-mcp.cjs— Node wrappers (tsx ESM loader in-process)bin/node-guard.cjs— under a project's older node (.nvmrcwith fnm, nvm, mise), runs the wrapper again on the node beside its bin link
Not implemented yet
- Above L1 the
ciaxis reads GitHub Actions only; other CI systems answer "unknown".branch-protectionreads GitHub, GitLab and Bitbucket Server; other hostings (gitolite, Bitbucket Cloud) answer "unknown", and so do Bitbucket restrictions by pattern or branching model. - Baseline loading from a git ref is documented but evaluator still reads file
paths only —
baseline:inmise.tomlis recorded but does not auto-fetch. - Watermark drift (phase 2 of
drift-algorithm.md). vdx publishonly ships for Node (MVP). PHP/Python in Y.3; Cargo/Ruby/Go/Java in Y.4.- Open items: O25 (mock-infra delta for Node), O26 (TOML round-trip), O27 (real shared-infra precheck), O32 (multi-subpackage monorepo). See docs/decisions.md.
