Skip to main content

Define and run named commands per project with runit.yaml

Project description

runit

PyPI version Python License: MIT

Stop typing the same long commands over and over. runit lets you save commands with short names and run them instantly.

runit demo

runit add build "cargo build --release"
runit build

That's it. No config files to manage, no setup step. Just add a command and run it.

Install

pip install runit-dev

Requires Python 3.10+. Works on macOS, Linux, and Windows.

Quick start

# Save a command
runit add test "pytest -v --tb=short"

# Run it
runit test

# See what you've got
runit list

Multiple steps

Multi-step execution

Commands can be a sequence. They run in order, stopping if any step fails.

runit add deploy "npm run build" "npm test" "rsync -av dist/ server:/var/www/"

runit deploy
# $ npm run build
# $ npm test
# $ rsync -av dist/ server:/var/www/

Parameters

Parameters

Some commands are almost the same every time, just with a different value here and there. Use {var} placeholders and pass values positionally - just type the values after the command name, in order.

runit add deploy "kubectl apply -f k8s/{env}.yaml" "echo 'deployed to {env}'"

runit deploy staging
# $ kubectl apply -f k8s/staging.yaml
# $ echo 'deployed to staging'

runit deploy prod
# $ kubectl apply -f k8s/prod.yaml
# $ echo 'deployed to prod'

Multiple parameters just go in order:

runit add ssh "ssh {user}@{host}"

runit ssh admin 192.168.1.10
# $ ssh admin@192.168.1.10

You can set defaults with {var:default} - if you don't pass it, the default kicks in.

runit add push "docker push myapp:{tag:latest}"

runit push              # uses tag=latest
runit push v2.0         # uses tag=v2.0

Strings with spaces work fine in quotes:

runit add greet "echo {message}"

runit greet "hello world"
# $ echo hello world

If you forget a required parameter, runit tells you what's missing:

$ runit deploy
Missing: env
Usage: runit deploy <env>

Capture output as variables

Capture variables

Sometimes a later step needs the output of an earlier one. Prefix a step with @varname to capture its stdout into a variable, then use {varname} in any step that follows.

runit add whoami "@user whoami" "echo Hello, {user}"

runit whoami
# $ @user whoami
#   user = itayfliess
# $ echo Hello, itayfliess
# Hello, itayfliess

A more practical example - grab your local IP and use it:

runit add myip "@ip ifconfig en0 | grep 'inet ' | awk '{print \$2}'" "echo {ip}"

runit myip
# $ @ip ifconfig en0 | grep 'inet ' | awk '{print $2}'
#   ip = 192.168.1.5
# $ echo 192.168.1.5
# 192.168.1.5

Captures can be combined with regular parameters:

runit add deploy "@host cat config/{env}.txt" "scp build.tar {host}:/app/"

runit deploy staging

If a capture step fails, execution stops - same as any other step.

Random mode

Random mode

Pick a random command from a list each time you run it.

runit add tip "echo 'commit often'" "echo 'write tests'" "echo 'take a break'" --mode random

runit tip
# $ echo 'take a break'   (random pick)

Built-in commands

runit ships with a set of built-in git/dev commands ready to use out of the box. They show up under "Built-in" in runit list and work in any project.

Command Description
runit prune Delete local branches whose remote has been deleted
runit untrack <path> Remove a file from git tracking (without touching .gitignore)
runit loc [ext] Count lines of code by file type (default: .py)
runit glog Pretty git log graph with author, date, and branch info
runit prune
# Fetches remote and deletes any local branches marked as gone

runit untrack src/secret.txt
# $ git rm --cached -r src/secret.txt

runit loc
# Counts all .py files

runit loc ts
# Counts all .ts files

runit glog
# Pretty git log graph with colors, authors, dates, and branch refs

You can override any built-in by adding a project or global command with the same name, or disable one entirely:

runit remove glog        # Disable a built-in for this project
runit remove -g glog     # Disable a built-in everywhere

To re-enable a disabled built-in, override it or add it back manually.

Global commands

By default, commands are scoped to your project. If you want a command available everywhere, add it with -g:

runit add -g gs "git status -sb"
runit add -g gp "git push"

# Now 'runit gs' works in any directory

Project commands take priority - if you have a global build and a project build, the project one wins.

runit list           # shows both global and project commands
runit list -g        # shows only global commands

Sharing commands

Got a couple of commands you want to send to a teammate? runit export packs them into a single copy-pasteable code, and runit import unpacks it on the other side.

runit add dev-pc "flutter run -d macos --dart-define=FORCE_MODE=pc"
runit add dev-mobile "flutter run -d macos --dart-define=FORCE_MODE=mobile"

runit export dev-pc dev-mobile
# runit:v1:Y29tbWFuZHM6CiAgZGV2LXBjOiBmbHV0dGVyIHJ1bi...
# Share with: runit import <code>  (2 command(s))

The first line is the code itself (stdout); the hint goes to stderr, so you can pipe straight to your clipboard:

runit export dev-pc dev-mobile | pbcopy           # macOS
runit export dev-pc dev-mobile | xclip -selection clipboard   # Linux

Run runit export with no names to share everything you've got saved.

On the receiving end, paste the code into runit import. You'll be asked whether to save it to this project or globally:

runit import "runit:v1:Y29tbWFuZHM6..."
# Importing: dev-pc, dev-mobile
# Rename any of these? [y/N]:
# Save to (local, global) [local]:
# Imported 2 command(s) into project: dev-pc, dev-mobile

Skip the prompt with an explicit flag:

runit import "runit:v1:..." -l       # save to this project
runit import "runit:v1:..." -g       # save globally

If a name already exists, the import skips it and tells you. Pass --force to overwrite.

You can also pipe the code in instead of passing it as an argument:

pbpaste | runit import -l

Renaming on import

Whenever the code includes names, runit import asks Rename any of these? [y/N]. Hit Enter to keep them as-is; answer y to step through each command and pick a new name (Enter accepts the original):

Importing: dev-pc, dev-mobile
Rename any of these? [y/N]: y

dev-pc
  flutter run -d macos --dart-define=FORCE_MODE=pc
Name [dev-pc]: pc

dev-mobile
  flutter run -d macos --dart-define=FORCE_MODE=mobile
Name [dev-mobile]:

Sharing without names

Pass -N to runit export to strip the names. Useful when your local names are personal (my-quick-thing) and the teammate should pick their own:

runit export -N dev-pc dev-mobile

The importer is then asked to name each one, with the steps shown for context:

runit import "runit:v1:..."
Naming 2 command(s) from the share code:

Command 1 of 2:
  flutter run -d macos --dart-define=FORCE_MODE=pc
Name: pc

Command 2 of 2:
  flutter run -d macos --dart-define=FORCE_MODE=mobile
Name: mobile

Multi-step commands, parameters, capture chains, and --mode random all survive the round-trip — the share code carries the full command structure, not just the shell text.

Inspecting commands

Use show to see the full details of a command - where it's stored, its mode, parameters, and every step.

runit show deploy
# deploy
#   source:  project (.git/runit.yaml)
#   mode:    sequential
#   params:  env (required), tag (default: latest)
#   steps:
#     1. kubectl apply -f k8s/{env}.yaml
#     2. echo 'tag: {tag:latest}'

runit show myip
# myip
#   source:  project (.git/runit.yaml)
#   mode:    sequential
#   captures: ip
#   steps:
#     1. @ip ifconfig en0 | grep 'inet ' | awk '{print $2}'
#     2. echo {ip}

Editing commands

Update a command without having to remove and re-add it.

# Replace the steps
runit edit deploy "new-step-1" "new-step-2"

# Change just the mode
runit edit deploy --mode random

# Update both steps and mode
runit edit deploy "new-cmd" --mode sequential

# Edit a global command
runit edit -g gs "git status -sb --porcelain"

Renaming commands

runit rename test t          # rename project command
runit rename -g gs gst       # rename global command

Removing commands

runit remove test           # remove project command
runit remove -g gs          # remove global command

Resetting

Clear all commands at once.

runit reset          # clear project commands
runit reset -g       # clear global commands
runit reset -a       # clear both project and global

Storage modes

runit has two storage modes. Switch with runit config storage_mode <mode>.

Repo mode (default)

Commands live with the project:

  • Git projects - inside .git/runit.yaml (invisible, not tracked)
  • Other directories - in ~/.cache/runit/, keyed by folder

Folder mode

All commands stored centrally, keyed by folder path:

  • macOS/Linux - ~/.config/runit/projects/
  • Windows - %APPDATA%/runit/projects/
runit config storage_mode folder    # switch to folder mode
runit config storage_mode repo      # switch back
runit config storage_mode           # show current mode

Folder mode is useful if you want commands to survive repo deletion or work the same way regardless of git.

Global commands

Stored separately from project commands, always available everywhere:

  • macOS/Linux - ~/.config/runit/runit.yaml
  • Windows - %APPDATA%/runit/runit.yaml

Settings

View or change any setting with runit config <key> [value].

single_command

When your project has exactly one saved command (globals and built-ins don't count), runit can run it automatically when you type runit with no arguments.

runit config single_command run     # auto-run when only one command exists
runit config single_command ignore  # do nothing (default)
runit config single_command         # show current value

storage_mode

Controls where project commands are saved. See Storage modes above.

runit config storage_mode folder
runit config storage_mode repo

All commands

Command Description
runit <name> [args] Run a saved command
runit add <name> "cmd" ... Save a new command
runit edit <name> "cmd" ... Update an existing command
runit show <name> Show full command details
runit rename <old> <new> Rename a command
runit remove <name> Remove a command
runit reset Clear all commands
runit list List all commands (built-in, global, project)
runit export [names...] Pack commands into a copy-pasteable share code
runit import <code> Unpack a share code into this project or globally
runit config <key> [val] View or change settings

Add -g to add, edit, rename, remove, list, or reset to target global commands. On runit export, -g looks names up in global commands; on runit import, -g/-l choose where to save (otherwise you're asked).

Step syntax

Syntax Meaning
{var} Required parameter - pass positionally when running
{var:default} Optional parameter with a default value
@varname cmd Capture step - runs cmd, stores stdout as varname

Going further

See Advanced usage & recipes for capture chains, multi-directory workflows, and real-world command patterns.

Credit

Credit to @Eyalcfish for the idea and its based on his pulse project.

Disclaimer

AI was used to generate almost all of this tool.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

runit_dev-0.1.6.tar.gz (22.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

runit_dev-0.1.6-py3-none-any.whl (20.0 kB view details)

Uploaded Python 3

File details

Details for the file runit_dev-0.1.6.tar.gz.

File metadata

  • Download URL: runit_dev-0.1.6.tar.gz
  • Upload date:
  • Size: 22.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for runit_dev-0.1.6.tar.gz
Algorithm Hash digest
SHA256 64ba08d860994f9242a6ccd5c6a64e034f4e4dcb501f8a567f30ccf0ca0dbc7c
MD5 d659c8dab32bb5cf8f21c81eb8360c3d
BLAKE2b-256 7fa53ae722156234ac16bf2ba6b4a8ce5f5daea755d8127cb173c4e07e6a98ba

See more details on using hashes here.

File details

Details for the file runit_dev-0.1.6-py3-none-any.whl.

File metadata

  • Download URL: runit_dev-0.1.6-py3-none-any.whl
  • Upload date:
  • Size: 20.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.3

File hashes

Hashes for runit_dev-0.1.6-py3-none-any.whl
Algorithm Hash digest
SHA256 12fbc87d658301bb6fd212b03c4b08e0ac03b1d45667236fa2458e8f7bec8e34
MD5 b26c8da865e45336e4c9496a183c698c
BLAKE2b-256 fdf9a2b4c8ee6c1c4aed8d2643287e38814078057ebd690bfefbc4aa060b5ba1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page