Skip to content

KitsuneKode/run-cli

Repository files navigation

run-cli

run-cli gives a project one stable command surface: run.

Instead of remembering whether this repo uses bun run, node, python, go run, cargo run, or a shell script, you define the project contract once in .run.toml and use the same launcher everywhere.

Core idea

run has one job: launch the project's command intentionally and cheaply.

  • run executes the project's default command
  • run -p <profile> executes a named profile
  • run -- <args...> forwards args to the child command untouched
  • run up manages long-running processes started by run
  • run doctor explains what the CLI resolved

The current version is built around those rules. This is the canonical contract.

Mental model

Think about the CLI in three layers.

1. Project command

Plain run means:

run

That executes the effective default profile from .run.toml.

2. Profile selection

Profiles are explicit:

run -p dev
run -p worker

If you want a named workflow, use -p or --profile.

3. Child arguments

Anything after -- belongs to your app, not the CLI:

run -- --watch
run -p dev -- --port 3000
run -- doctor

This is how you pass words like doctor, inspect, or ports to the underlying project command instead of triggering run subcommands.

Rule of thumb:

  • before -- = run CLI territory
  • after -- = child command territory

Why it exists

Most repos already have a real entrypoint, but the entrypoint is hidden behind ecosystem-specific commands and local habits.

run-cli makes that entrypoint explicit and versioned.

It is intentionally:

  • lightweight: Bun runtime, no runtime deps, cheap local state
  • explicit: profiles are named, config is local, no magic env activation
  • deterministic: dry-run, doctor output, and managed process metadata all come from the same resolved command path
  • agent-friendly: the tool exposes stable human and machine-readable diagnostics

Install

Prerequisite: Bun >= 1.3.9

bun add -g @kitsunekode/run-cli

You can also install with npm if Bun is already available on your PATH at runtime:

npm install -g @kitsunekode/run-cli

That exposes:

  • run

Install from source

bun install
bun run build
bun link

Refresh the global link after local updates:

bun run relink:global

Release Workflow

  • Add a changeset with bun run changeset for user-facing or release-worthy changes.
  • Work from short-lived branches and merge to main.
  • .github/workflows/version-packages.yml opens or updates a Version Packages PR from merged changesets.
  • Merging the version PR updates package.json and changelog entries for the next release.
  • CI runs on pull requests and main, and its workflow summary records the exact package version it validated.
  • Release runs on pushes to main and manual dispatch. It publishes the exact package.json version of @kitsunekode/run-cli through npm trusted publishing and creates a GitHub release tagged vX.Y.Z.
  • For a brand-new package, do one manual bootstrap publish first so the npm package settings page exists and you can attach the trusted publisher to release.yml.

Quickstart

Inside a project:

run init
run
run -p dev
run -- --watch

Minimal config example:

version = 1
default_profile = "default"

[profiles.default]
command = "bun run index.ts"

[profiles.dev]
command = "bun --hot index.ts"

How to use it day to day

Run the project

run

Run a named workflow

run -p dev
run -p worker

Pass child arguments

run -- --watch
run -p dev -- --port 3000
run -- inspect

See exactly what would run

run --dry-run
run -p dev --dry-run
run -- --watch --dry-run

Ask the CLI what it resolved

run doctor
run doctor --json
run config validate

Manage a background process

run up
run up -p worker -- --port 4000
run ps
run ps --details
run dashboard
run inspect my-app:worker
run logs my-app:worker --follow
run ports
run stop my-app:worker
run restart my-app:worker
run prune

CLI overview

run [args...] [-p <profile>] [-v] [--dry-run] [--no-cache] [--config <path>] [--cwd <path>]
run init [--force] [--yes] [--command <cmd>] [--default-profile <name>] [--add-profile <name=command>]
run completion <zsh|bash>
run doctor [--json]
run profiles [--json]
run up [args...] [-p <profile>] [--name <name>]
run ps [--json] [--details] [--watch]
run dashboard
run inspect <name|id> [--json]
run logs <name|id> [--lines <n>] [--follow]
run stop <name|id>
run restart <name|id>
run kill <name|id>
run prune [--json] [--dry-run]
run ports [--json]
run config <view|path|edit|validate> [--global]
run help

First-party commands vs child args

These are built into run:

  • init
  • completion
  • doctor
  • profiles
  • up
  • ps
  • dashboard
  • inspect
  • logs
  • stop
  • restart
  • kill
  • prune
  • ports
  • config
  • help

If you want the underlying project command to receive one of those words as an argument, use --:

run -- doctor
run -- inspect
run -p dev -- ports

Project config

The canonical project config file is:

  • .run.toml

Legacy support still exists for:

  • .run.config.toml

But .run.toml is the real contract and the file you should create, commit, and document.

run walks upward from the current directory and uses the nearest project config only.

That means:

  • monorepo packages can own their own run config
  • repo roots can still provide a shared default
  • ancestor configs are not merged

Example .run.toml

version = 1
default_profile = "dev"

[profiles.default]
command = "bun run src/index.ts"

[profiles.dev]
command = "bun --hot src/index.ts"
description = "local development server"

[profiles.worker]
command = "bun run src/worker.ts"
cwd = "services/worker"

[profiles.worker.env]
QUEUE_NAME = "jobs"
DEBUG = true

Output model

The CLI tries to stay polished without becoming noisy.

  • default execution prints a compact startup banner
  • --verbose / -v adds resolution details like profile, cwd, config path, and cache state
  • --dry-run prints the exact shell command
  • run doctor is the readable diagnostic report
  • run doctor --json is the machine-readable diagnostic report

doctor --json shape

run doctor --json currently returns:

  • cwd: current effective working directory
  • configLookup: resolved config metadata or null
  • globalConfigPath: global config path
  • cacheFilePath: cache file path
  • shell: effective shell
  • cacheEnabled: whether cache is enabled
  • detectedProject: detected project metadata or null

configLookup, when present, contains:

  • sourcePath
  • cacheHit
  • legacy

detectedProject, when present, contains:

  • root
  • markers
  • cacheHit
  • suggestions

Managed processes

run up stores a lightweight registry for processes it starts itself.

Managed process metadata includes:

  • project name
  • profile
  • full resolved command
  • forwarded args
  • pid
  • cwd
  • config path
  • log path
  • restart count

Performance behavior is intentional:

  • run ps shows memory usage by default with color-coded status (green=running, yellow=stopped, red=exited)
  • run ps --watch live-refreshes the process list every 2 seconds
  • run ps --details adds port information alongside metrics
  • run inspect and run ports opt into more detailed process information
  • run prune removes dead (exited/stopped) processes from the registry
  • Memory thresholds warn at 512MB (yellow) and 1GB (red) in the MEM column

This keeps the common path fast and low-overhead.

Diagnostics and debugging

Use these when something feels off:

run doctor
run doctor --json
run config path
run config view
run config validate
run --dry-run

Typical workflow:

  1. run config path to confirm which config is active
  2. run config validate to confirm it parses
  3. run --dry-run to see the final command
  4. run doctor if you need the full resolution story

Shell completion

Generate completion scripts from the CLI:

mkdir -p ~/.config/zsh/completions
run completion zsh > ~/.config/zsh/completions/_run
run completion bash > ~/.config/bash/run.bash

For Zsh:

fpath=(~/.config/zsh/completions $fpath)
autoload -Uz compinit
compinit

Checked-in loader scripts also exist in:

  • completions/run.zsh
  • completions/run.bash

Global config

Global defaults live at:

  • $XDG_CONFIG_HOME/run/config.toml
  • fallback: ~/.config/run/config.toml

Global config is intentionally limited to defaults such as:

  • shell
  • editor
  • cache
  • detection

It does not define project commands or project profiles.

Example:

version = 1
shell = "/bin/zsh"
editor = "code -w"
cache = true
detection = "suggest"

Caching

Cache data is written to:

  • $XDG_CACHE_HOME/run/cache.json
  • fallback: ~/.cache/run/cache.json

The cache stores cheap metadata such as:

  • resolved config paths for known working directories
  • detection results for known project roots

Use --no-cache to bypass cache reads and writes.

Ecosystem examples

Bun / TypeScript

version = 1
default_profile = "dev"

[profiles.default]
command = "bun run src/index.ts"

[profiles.dev]
command = "bun --hot src/index.ts"

Node / JavaScript

version = 1

[profiles.default]
command = "node index.js"

Python

version = 1

[profiles.default]
command = "python exp.py"

[profiles.dev]
command = "python -m uvicorn app:app --reload"

If project-native tooling is present, run init prefers explicit commands like:

  • uv run python main.py
  • pipenv run python main.py
  • poetry run python main.py
  • .venv/bin/python main.py

Go

version = 1

[profiles.default]
command = "go run ."

Contributing and docs

Useful repo docs:

  • docs/config-reference.md
  • docs/architecture.md
  • docs/contributing.md
  • AGENTS.md

Local quality workflow:

bun run format
bun run lint
bun test
bun run build

Current scope

  • Bun is required to run the CLI
  • v1 targets macOS and Linux shell behavior
  • Windows support is intentionally deferred

About

A lightweight Bun-native CLI that turns project startup into a single run command, with explicit profiles, smart detection, shell completions, and local process management.

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors