Every command carries its own documentation.
npm scripts, Makefiles and half-remembered dotnet run incantations tell
you that a command exists. They never tell you what it does, what arguments it takes,
or what it produces. noodge keeps the
answer next to the command, where it cannot drift.
noodge ~/dev/noodge/noodge.yaml > build │ build test │ test:race │ Compiles every package. check │ lint │ The fastest check that a change has not broken anything gen-schema │ structurally. Does not produce a binary; use install for install │ that. │ │ Output │ Nothing on success. Compiler errors on failure. │ │ Steps │ 1. go build ./... up/down move enter run / filter q quit
That is noodge, run with no arguments, in its own repository.
Arrow through the commands, read what each one does, press enter.
The problem
You already have these commands.
You just cannot read them. Here is an ordinary package.json. Every name
is a guess, and the only way to learn what gen produces is to run it and find out.
What you have
"scripts": { "dev": "concurrently \"npm:dev:*\"", "dev:api": "tsx watch src/server.ts", "gen": "openapi-gen -i ./spec.yaml -o ./src/api", "db:reset": "docker compose down -v && prisma migrate reset --force", "predeploy": "npm run build && node ./scripts/check-env.mjs" }
Which one do I run first? Does db:reset touch the shared database or
the local one? Is gen safe to run on a dirty tree? Nobody knows, so someone asks in
Slack, and the answer lives in that thread until it scrolls away.
What noodge gives you
my-api ~/dev/my-api/noodge.yaml dev Runs the whole stack locally: api, worker and web. dev:api Restarts one service without touching the others. start:local Starts the API behind a local HTTPS listener. gen Regenerates the typed API client from spec.yaml. db:reset Drops and recreates the local database, then reapplies every migration. db:stop Stops the local Postgres container, keeping its data. deploy Deploys the current commit to production.
Same commands, same project. The documentation lives in the config next to the
thing it describes, so it cannot drift — and it shows up everywhere you might look: the
browser, --help, noodge list, and your shell's tab completion.
Get it
Install it in ten seconds.
One static binary. No runtime to install first, nothing to add to your project. The scripts verify the download's SHA-256 against the published checksums before installing anything, and neither needs administrator rights.
$ curl -fsSL https://noodge.dev/install.sh | sh
> irm https://noodge.dev/install.ps1 | iex
> scoop bucket add wimhaanstra https://github.com/wimhaanstra/scoop-bucket > scoop install noodge
$ go install github.com/wimhaanstra/noodge/cmd/noodge@latest
# Download an archive from the releases page and put the binary on your PATH. # Windows, macOS and Linux, on amd64 and arm64. https://github.com/wimhaanstra/noodge/releases
noodge.yaml with two working commands in it.# yaml-language-server: $schema=https://wimhaanstra.github.io/noodge/schema/v1/noodge.schema.json version: 1 name: starter commands: hello: description: | A placeholder so you can check noodge works. Replace this with something your project actually needs, and write the description for whoever joins next month. steps: - echo Hello from noodge output: One line on stdout.
Already have it? noodge upgrade replaces the binary in place — and
refuses politely when a package manager put it there, telling you the right command to use
instead. No telemetry: noodge makes no network requests other than an update
check you can turn off.
Browse, don't remember
A browser that teaches the command line.
Commands group into families by the part of the name before the colon, so the list stays navigable at forty commands. Give a family a title and a line of explanation and the browser shows those too.
fleetgo-exact-gateway ~/dev/gateway/noodge.yaml Running it │ Database dev │ dev:api │ The local Postgres the gateway develops against. dev:worker │ > Database │ 2 commands in this group. db:reset │ db:stop │ up/down move enter run / filter q quit
Confirming prints the equivalent command line before running it, so the browser teaches the command line rather than replacing it.
$ noodge start:local --certificate dev.pfx --host localhost --verbose
The browser never runs anything itself. It hands back a command name and its
arguments, so the command inherits the real terminal — it keeps its colours, it can prompt
for input, and it reports its own exit code. In a pipe, in CI, or anywhere without a terminal,
noodge lists instead. That is the correct answer rather than a
fallback.
Write it once
The config is the help text.
There is no second place to update, so nothing can fall out of step. What you write on the left is exactly what the next person reads on the right.
test: description: | Runs the whole test suite. Extra arguments are passed straight through to `go test`, so `noodge test -- -run TestDiscover -v` works without editing this file. steps: - go test ./... output: One line per package, then ok or FAIL.
Runs the whole test suite. Extra arguments are passed straight through to `go test`, so `noodge test -- -run TestDiscover -v` works without editing this file. Output: One line per package, then ok or FAIL. Steps: go test ./... Arguments after -- are appended to the last step.
The same description reaches the browser, noodge list, and your
shell's tab completion — which is per-directory, so tabbing in one project never offers
you another project's commands.
Nothing runs until it is valid
Parameters are declared, not parsed by hand.
Give a parameter a type and noodge checks it before a single process starts. A bad value is a clean refusal with exit code 2, not a stack trace from whatever you wrapped.
params: - name: certificate flag: --certificate short: -c type: path required: true description: Path to the .pfx used for the local HTTPS listener. steps: - node serve.ts {{flag host}} {{flag certificate}}
Flags: -c, --certificate string Path to the .pfx used for the local HTTPS listener. (required) --host string Hostname the server binds to. (default: localhost) -v, --verbose Enables per-request logging.
$ noodge start:local --dry-run noodge: required flag "--certificate" is not set $ noodge start:local -c nope.pfx --dry-run noodge: --certificate: nope.pfx does not exist $ noodge start:local -c dev.pfx --verbose --dry-run start:local would run in ~/dev/my-api: 1. node serve.ts --host "localhost" --certificate "dev.pfx" --verbose with environment: NOODGE_COMMAND=start:local NOODGE_PARAM_CERTIFICATE=dev.pfx NOODGE_PARAM_HOST=localhost NOODGE_PARAM_VERBOSE=true
Every refusal happens before any process starts, and every value reaches the
wrapped tool shell-quoted. noodge deploy --host 'a && shutdown /s' arrives as one
literal argument. It is data, and it stays data. Use --dry-run
whenever you want to see the exact command lines without running them.
| Placeholder | When the parameter is | Expands to |
|---|---|---|
| {{flag host}} | set, or defaulted | --host localhost |
| {{flag host}} | optional and unset | nothing at all |
| {{flag verbose}} | a bool, and true | --verbose |
| {{flag verbose}} | a bool, and false | nothing at all |
| {{host}} | set | localhost |
| {{args}} | — | whatever you typed after -- |
Steps that do more than run in order
Start things together. Stop them together.
Steps run one after another by default. Work that does not depend on anything else
goes in a parallel: group, and each line of output is labelled with who said it.
dev: description: Runs the whole stack locally. steps: - npm install # sequential, as usual - parallel: api: npm run dev:api worker: npm run dev:worker web: npm run dev:web
$ noodge dev added 214 packages in 3s api | listening on :3000 worker | polling for jobs web | vite ready in 412 ms
Everything a group starts goes into one process tree, so stopping it also stops
what those processes started. This is the part that is easy to get wrong on
Windows, which has no process groups: killing npm there leaves
node holding the port. noodge uses a Job Object with kill-on-close, so the whole
tree goes.
And it asks before anything you cannot undo.
db:reset: description: Drops and recreates the local database. confirm: true # the default prompt steps: - docker compose down -v deploy: description: Deploys to production. confirm: This deploys to PRODUCTION. Continue? steps: - ./scripts/deploy.sh --env prod
$ noodge db:reset Really run "db:reset"? [y/N] $ noodge deploy This deploys to PRODUCTION. Continue? [y/N] n $ noodge deploy --yes # no prompt — for CI and scripts
Declining exits 2, so a script that says no fails rather than carries on quietly. Step exit codes otherwise pass through untouched — noodge never invents one.
When you get it wrong
It tells you where, and what to do about it.
Every problem in one pass, each with a line, a column and a hint. Not the first one, then a rerun, then the next one.
noodge.yaml:8:15: error: flag "-host" must start with two dashes hint: write it as --host. This is only how you type it to noodge; a step is still free to write -host {{host}} to pass it on with a single dash noodge.yaml:10:13: error: parameter "mode" is an enum but lists no values hint: add a values: list of the allowed values noodge.yaml:18:9: error: step 1 refers to {{nosuch}}, but command "bad" declares no parameter called "nosuch" hint: add it under params:, or correct the name noodge.yaml:14:13: error: parameter "unused" is declared but no step uses it hint: add {{flag unused}} to a step, or remove the parameter noodge.yaml:20:3: error: command "nosteps" has no steps, so there is nothing to run hint: add a steps: list noodge.yaml: 5 errors, 0 warnings
The JSON Schema ships with the binary, and noodge init puts a modeline
at the top of the file pointing at it — so your editor catches most of this before you
save. Run noodge schema to print it.
The rest of it
Small things you notice on day two.
None of these are why you would install it. All of them are why you would keep it.
Found, not configured
Walks up from your working directory to the nearest config, the way git finds
.git, and stops at a repository boundary. Commands run in the directory holding
the config, not wherever you happened to be standing.
noodge build # from any subdirectory
Correct without a terminal
In a pipe, in CI, or anywhere there is no TTY, noodge lists instead of opening the browser. There is a machine-readable form for editors and scripts.
noodge list --json
Values stay data
Every parameter value is shell-quoted on the way out, so a value that looks like a shell operator reaches the wrapped tool as one literal argument. There is no injection surface.
--host "a && shutdown /s"
Completion that knows the project
Tab through this directory's commands, descriptions included. bash, zsh, fish, pwsh and Windows PowerShell — and it will set itself up for you.
noodge completion install zsh
Windows done properly
Two quoting parsers reconciled, because cmd.exe escapes an embedded quote as
"" while the C runtime expects \". A Job Object with kill-on-close
so nothing keeps the port. Each step its own process, so no && is needed.
windows · macos · linux · amd64 · arm64
One static binary
No runtime, no cgo, nothing added to your project but a YAML file. Every glyph in the interface is ASCII, and the colours are legible on a light and a dark background alike.
noodge version
No telemetry. noodge collects nothing and phones home to nobody.
The only network request it ever makes is an update check, and
NOODGE_NO_UPDATE_CHECK=1 turns that off.