* fix(cli): do not report success when sync push drops a script metadata change `sync push` skipped every added `.script.yaml` / `.script.json` / `.script.lock` on the assumption that the sibling content file in the same group carried the deploy. When the content file was not in the changeset — e.g. filtered out by `excludes` — nothing was sent to the remote, yet the push still printed "Done! All N changes pushed" and exited 0, so CI gating on the exit code went green on a deploy that never happened. Route those changes through `handleScriptMetadata` (as the "edited" branch already does), which resolves the content file from disk and deploys it. The deploy stays idempotent via `alreadySynced`, and a metadata file with no content file now fails the push instead of being counted as pushed. Fixes WIN-2254 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): treat only the module entry point as script metadata `handleScriptMetadata` / `findContentFile` matched any `script.yaml`, `script.json` or `script.lock` anywhere under a `__mod/` tree, so a module file nested deeper (e.g. `f/foo__mod/config/script.yaml`) was mistaken for the script's own metadata. Gate on `isModuleEntryPoint`, which requires the file to sit directly under `__mod/`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): reject non-metadata paths in findContentFile Every candidate-path replacement in findContentFile is a no-op on a path that is neither flat `*.script.{yaml,json,lock}` nor a module entry point, so the input resolved to itself and the caller got a "more than one candidate found" list of 25 copies of the same path. Reject those inputs up front. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): report an unpushable script as a failure instead of aborting the push Throwing out of the apply loop for a metadata file with no script file left the push partially applied: every change queued behind it was dropped, including ones with nothing wrong. Collect these into a failed list, log each one, keep applying the rest, and report `N of M changes pushed; K failed` with a non-zero exit (`success: false` plus a `failed` array under --json-output). Only the content-resolution failure is soft, via MissingScriptContentFileError. A deploy the remote rejected still aborts, since it says nothing about whether the remaining changes are safe to apply. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): let stdout drain before sync push returns a failure exit code process.exit does not wait for a pending piped stdout write, so a --json-output push with enough changes was cut off at the 64KiB pipe buffer, handing CI consumers unparseable JSON. Set process.exitCode instead and return normally, matching how main.ts already reports failures. Reproduced with a 1904-change push: process.exit truncated the result at exactly 65536 bytes; process.exitCode emits all 432KiB and still exits 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(cli): treat an ambiguous script file as a recoverable push failure findContentFile has two ways to fail to pair a metadata file with a script file, and only the "none found" one threw the class sync push catches. Two script files for the same name (a .ts and a .py both being in exts) therefore still aborted the whole push, dropping every change queued behind it — the failure mode failedChanges exists to prevent, newly reachable now that an added .script.yaml reaches findContentFile at all. Both branches now throw the same class, renamed to UnresolvableScriptContentFileError since it no longer only covers a missing file, and the ambiguous case gets a message that names the clashing files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Windmill CLI
A simple CLI allowing interactions with windmill from the command line.

You can find more information in Windmill Docs
Installation
Install the wmill CLI tool using npm install -g windmill-cli.
Update to the latest version using wmill upgrade.
Workspaces
To get started run wmill workspace add or use the instructions from the
workspace settings.
Running Flows & Scripts
Run a script or flow using wmill flow/script run u/username/path/to/script and
pass any inputs using --data + Inputs specified as a JSON string or a file
using @ <filename> or stdin using @-.
Curl-style syntax using -d @- for stdin or -d @<filename> is also supported.
Flow Steps and Logs will be streamed during execution automatically.
Pushing Resources, Scripts & More
The CLI can push specifications to a windmill instance. See the examples/ folder for formats.
Switch to a different workspace
wmill workspace switch <workspace_name>
Sync a workspace
Pull
wmill sync pull
Push
wmill sync push
We recommend using the --yaml option to use yaml instead of json as the encoding format. Yaml will be made the default soon.
Pushing individual files
You can push individual resources using
wmill <type> push <file_name> \<remote_name\>. This does not require a special
folder layout or file name, as this is given at runtime.
Listing
All commands support listing by just not providing a subcommand, ie
wmill script will result in a list of scripts. Some allow additional options,
learn about this by specifying --help.
User Management
You can add & remove users via wmill user add/remove, and list them using
wmill user
Pulling
You can pull the entire workspace using wmill pull
Completion
The CLI comes with completions out of the box via wmill completions <shell>.
(Via cliffy)
Bash
To enable bash completions add the following line to your ~/.bashrc:
source <(wmill completions bash)
Fish
To enable fish completions add the following line to your
~/.config/fish/config.fish:
source (wmill completions fish | psub)
Zsh
To enable zsh completions add the following line to your ~/.zshrc:
source <(wmill completions zsh)
Development
AI Guidance Variants
wmill init can now materialize alternate AI guidance bundles without changing
the generated defaults in the repo, but this is exposed as internal env-var
overrides rather than public CLI flags.
Examples:
WMILL_INIT_AI_SKILLS_SOURCE=/path/to/custom/skills wmill init --use-default
WMILL_INIT_AI_SKILLS_SOURCE=/path/to/custom/skills WMILL_INIT_AI_AGENTS_SOURCE=/path/to/AGENTS.md wmill init --use-default
WMILL_INIT_AI_SKILLS_SOURCE=/path/to/custom/skills WMILL_INIT_AI_CLAUDE_SOURCE=/path/to/CLAUDE.md wmill init --use-default
This is the same guidance-writing path used by the benchmark CLI under
ai_evals/, so the benchmark harness and wmill init now generate the same
project guidance shape:
AGENTS.mdCLAUDE.md.agents/skills/*.claude/skills/*
Testing with a local windmill-yaml-validator
To test local changes to the validator before publishing, use npm link:
# In windmill-yaml-validator/
npm run build
npm link
# In cli/
npm link windmill-yaml-validator
Running Tests
Prerequisites:
- PostgreSQL running locally (default:
postgres://postgres:changeme@localhost:5432) - Rust toolchain installed
Run tests locally (full features):
bun test test/
Run tests in CI mode (minimal features, skips EE tests):
CI_MINIMAL_FEATURES=true bun test test/
| Variable | Description |
|---|---|
CI_MINIMAL_FEATURES |
Set to true to skip EE-dependent tests |
DATABASE_URL |
PostgreSQL connection string |
EE_LICENSE_KEY |
Enterprise license key for EE features |
