Run commands inside a named profile context. Each profile defines environment variables, setup hooks, preflight checks, and named scripts — all in a single TOML file.
When working across multiple environments (clusters, accounts, tenants), the risk is running a command against the wrong one. pro provides layered defense:
- Separate
KUBECONFIGper profile — structural isolation; no ambient state bleeds between profiles preflightchecks — assertions that run before everyexec/run; abort if the environment isn't what you expect- Active profile tracking —
pro setuprecords which profile is active;exec/runhard-fail if you call with a different profile name pro status— see what's active before you run anything
cargo build --release
cp target/release/pro /usr/local/bin/proOr via cargo install:
cargo install --path .pro init # scaffold a pro.toml in the current directory
pro config-help # show all available TOML keysAdd a profile to pro.toml:
[default]
shell = "sh"
[default.vars]
LOG_LEVEL = "info"
[profiles.dev]
description = "Local development"
[profiles.dev.vars]
APP_ENV = "dev"
API_URL = "http://localhost:3000"
LOG_LEVEL = "debug"
[profiles.dev.scripts]
start = "npm run dev"
test = "cargo test"Then:
pro list # show available profiles
pro show dev # show resolved config for dev
pro exec dev -- node app.js # run a command with dev vars
pro run dev start # run a named script
pro env dev # print export KEY=value for each varDefault search order (first found wins):
./pro.toml./.pro.toml~/.config/pro/config.toml
Override with -c / --config:
pro -c ./path/to/pro.toml run dev start[default]
shell = "sh"
[default.vars]
LOG_LEVEL = "info"
APP_NAME = "myapp"
BASE_URL = "http://localhost"
API_URL = "${BASE_URL}:3000" # $VAR / ${VAR} interpolation
[default.scripts]
lint = "eslint ."
[profiles.dev]
description = "Local development"
dir = "./packages/app" # working directory for scripts/exec
[profiles.dev.vars]
APP_ENV = "dev"
LOG_LEVEL = "debug" # overrides default
[profiles.dev.setup]
commands = [
"npm install",
"cargo fetch",
]
[profiles.dev.scripts]
start = "npm run dev"
test = "cargo test"
[profiles.stage]
description = "Staging environment"
[profiles.stage.vars]
APP_ENV = "stage"
API_URL = "https://stage.example.com"
[profiles.stage.scripts]
deploy = "./deploy.sh stage"Print all available profiles (default first).
Print the resolved configuration for a profile (defaults merged in). Omit profile to show [default].
Run the profile's preflight checks. Exits non-zero if any check fails. Omit profile to check [default].
Run a command with profile environment variables. Preflight runs first in the same subprocess, so env mutations in preflight are visible to the command. Omit profile to use [default].
pro exec -- node app.js # default profile
pro exec dev -- npm start # dev profile
pro exec dev --setup -- cargo build # run setup first
pro exec dev --skip-preflight -- cargo run # skip preflightFlags:
| Flag | Description |
|---|---|
--setup |
Run setup commands before the command |
--skip-preflight |
Skip preflight checks |
--ignore-active |
Warn instead of failing on active profile mismatch |
Run a named script from [profiles.<profile>.scripts]. Omit profile to use [default]. Omit script to list available scripts.
pro run # list scripts for default profile
pro run start # run default profile's "start" script
pro run dev start # run dev profile's "start" script
pro run dev test --setup # run setup firstFlags:
| Flag | Description |
|---|---|
--setup |
Run setup commands before the script |
--skip-preflight |
Skip preflight checks |
--ignore-active |
Warn instead of failing on active profile mismatch |
Run the profile's setup.commands in order. Stops on first failure. Omit profile to run [default] setup.
On success, writes .pro-active next to pro.toml to record the active profile. Add .pro-active to your .gitignore.
Print the currently active profile (set by pro setup):
active: dev (/path/to/.pro-active)
Or no active profile set if pro setup has not been run in this directory.
Print export KEY=value for each resolved profile variable. Useful for sourcing into a shell session.
eval "$(pro env dev)"Scaffold a pro.toml with commented placeholders in the current directory. Fails if one already exists.
Print a reference of all available TOML keys with descriptions.
Print a shell completion script. Supported: bash, zsh, fish, elvish, powershell.
pro completions zsh > ~/.zfunc/_proValues in [default] are inherited by all profiles. Profile values win on conflict; setup.commands and preflight.commands lists are concatenated (default runs first).
[default.vars]
LOG_LEVEL = "info"
[profiles.dev.vars]
LOG_LEVEL = "debug" # overrides defaultMerge behavior:
| Field | Behavior |
|---|---|
vars |
key-level merge; profile wins on conflict |
scripts |
key-level merge; profile wins on conflict |
setup.commands |
concatenated; default runs first |
preflight.commands |
concatenated; default runs first |
shell |
profile wins; falls back to default; then sh |
dir |
profile wins; falls back to default |
Use "default" as a profile name to target [default] directly:
pro show default
pro run default lintsetup.commands run in order when you call pro setup. Stop on first failure. Use setup for one-time or session-start work: authentication, dependency installation, tool version pinning.
[profiles.prod.setup]
commands = [
"kubelogin convert-kubeconfig -l azurecli",
"npm install",
]A command that exactly matches a script name runs that script's body:
[profiles.dev.scripts]
install = "npm install && cargo fetch"
[profiles.dev.setup]
commands = ["install"] # runs the install script bodypreflight.commands run before every exec and run in the same subprocess as the command. This means env mutations in preflight are visible to the command that follows — useful for shell-env-based version managers.
[profiles.dev]
shell = "bash"
[profiles.dev.preflight]
commands = [
"source ~/.nvm/nvm.sh",
"nvm use 20", # modifies PATH; exec'd command sees it
]Like setup commands, a preflight command that exactly matches a script name runs that script's body:
[profiles.prod.scripts]
check-context = "kubectl config current-context | grep -q prod-cluster"
[profiles.prod.preflight]
commands = ["check-context"]Skip with --skip-preflight. Run standalone with pro check [profile].
Set dir to run scripts, setup commands, and exec'd processes in a specific directory. ~ is expanded. Inherited from [default] and overridable per profile.
[profiles.frontend]
dir = "./packages/web"
[profiles.frontend.scripts]
dev = "npm run dev" # runs from ./packages/webUse $VAR or ${VAR} in var values. References are expanded against profile vars (resolved first) and the current process environment.
[default.vars]
BASE = "http://localhost"
API = "${BASE}:8080" # → http://localhost:8080
BIN = "$HOME/bin" # → /Users/you/binControls how scripts, setup commands, and preflight commands run. All three use the profile shell and share a subprocess during exec/run.
| Value | Invocation |
|---|---|
sh (default on Unix) |
sh -c "<cmd>" |
bash |
bash -c "<cmd>" |
nu |
nu --no-config-file -c "<cmd>" |
| other | <shell> -c "<cmd>" |
Profile vars are merged on top of the current process environment. Profile values override; everything else (including PATH) is preserved. All TOML types convert to strings:
PORT = 3000 # → "3000"
DEBUG = true # → "true"
TIMEOUT = 2.5 # → "2.5"Point each profile at its own kubeconfig file for structural isolation — no ambient context state can bleed across profiles.
[profiles.dev.vars]
KUBECONFIG = "~/.kube/configs/dev.yaml"
NAMESPACE = "myapp-dev"
[profiles.prod.vars]
KUBECONFIG = "~/.kube/configs/prod.yaml"
NAMESPACE = "myapp-prod"Run authentication once with pro setup. Use preflight to assert the environment is correct before every command.
[profiles.prod.scripts]
auth = "kubelogin convert-kubeconfig -l azurecli"
check-context = "kubectl config current-context | grep -q prod-cluster"
check-account = "az account show --query id -o tsv | grep -q $AZURE_SUBSCRIPTION_ID"
[profiles.prod.setup]
commands = ["auth"]
[profiles.prod.preflight]
commands = ["check-context", "check-account"]pro setup prod # authenticate once; records active=prod
pro exec prod -- kubectl get pods -n $NAMESPACE
pro run prod deploy # preflight validates context before every runpro setup records the active profile in .pro-active. exec and run refuse to proceed if the active profile doesn't match:
error: active profile is "prod" but exec was called with "dev"
hint: run `pro setup dev` to switch, or pass --ignore-active to proceed anyway
pro status # active: prod (/path/to/.pro-active)
pro exec dev -- ... # hard fails — run pro setup dev first| Code | Meaning |
|---|---|
0 |
Success |
1 |
General failure |
2 |
Config error |
3 |
Profile not found |
| child code | Propagated from child process |