@kitsunekode/run-cli
v0.1.0
Published
A lightweight Bun-native CLI for running projects through local profiles and smart detection.
Downloads
32
Maintainers
Readme
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.
runexecutes the project's default commandrun -p <profile>executes a named profilerun -- <args...>forwards args to the child command untouchedrun upmanages long-running processes started byrunrun doctorexplains 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:
runThat executes the effective default profile from .run.toml.
2. Profile selection
Profiles are explicit:
run -p dev
run -p workerIf 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 -- doctorThis is how you pass words like doctor, inspect, or ports to the underlying project command instead of triggering run subcommands.
Rule of thumb:
- before
--=runCLI 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-cliYou can also install with npm if Bun is already available on your PATH at runtime:
npm install -g @kitsunekode/run-cliThat exposes:
runrunx
runx is the fallback alias if run collides with something in your shell.
Install from source
bun install
bun run build
bun linkRefresh the global link after local updates:
bun run relink:globalRelease Workflow
- Add a changeset with
bun run changesetfor user-facing or release-worthy changes. - Work from short-lived branches and merge to
main. .github/workflows/version-packages.ymlopens or updates aVersion PackagesPR from merged changesets.- Merging the version PR updates
package.jsonand changelog entries for the next release. CIruns on pull requests andmain, and its workflow summary records the exact package version it validated.Releaseruns on pushes tomainand manual dispatch. It publishes the exactpackage.jsonversion of@kitsunekode/run-clithrough npm trusted publishing and creates a GitHub release taggedvX.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 -- --watchMinimal 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
runRun a named workflow
run -p dev
run -p workerPass child arguments
run -- --watch
run -p dev -- --port 3000
run -- inspectSee exactly what would run
run --dry-run
run -p dev --dry-run
run -- --watch --dry-runAsk the CLI what it resolved
run doctor
run doctor --json
run config validateManage 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 pruneCLI 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]
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 helpFirst-party commands vs child args
These are built into run:
initcompletiondoctorprofilesuppsdashboardinspectlogsstoprestartkillpruneportsconfighelp
If you want the underlying project command to receive one of those words as an argument, use --:
run -- doctor
run -- inspect
run -p dev -- portsProject 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 = trueOutput model
The CLI tries to stay polished without becoming noisy.
- default execution prints a compact startup banner
--verbose/-vadds resolution details like profile, cwd, config path, and cache state--dry-runprints the exact shell commandrun doctoris the readable diagnostic reportrun doctor --jsonis the machine-readable diagnostic report
doctor --json shape
run doctor --json currently returns:
cwd: current effective working directoryconfigLookup: resolved config metadata ornullglobalConfigPath: global config pathcacheFilePath: cache file pathshell: effective shellcacheEnabled: whether cache is enableddetectedProject: detected project metadata ornull
configLookup, when present, contains:
sourcePathcacheHitlegacy
detectedProject, when present, contains:
rootmarkerscacheHitsuggestions
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 psandrun dashboardare cheap overview commandsrun ps --detailsopts into richer overview metrics- they avoid expensive per-process memory and port probing by default
run inspectandrun portsopt into more detailed process informationrun pruneremoves dead (exited/stopped) processes from the registry
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-runTypical workflow:
run config pathto confirm which config is activerun config validateto confirm it parsesrun --dry-runto see the final commandrun doctorif you need the full resolution story
Shell completion
Generate completion scripts from the CLI:
run completion zsh > ~/.config/zsh/run.zsh
run completion bash > ~/.config/bash/run.bashFor Zsh:
[[ ! -f ~/.config/zsh/run.zsh ]] || source ~/.config/zsh/run.zshChecked-in loader scripts also exist in:
completions/run.zshcompletions/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:
shelleditorcachedetection
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.pypipenv run python main.pypoetry 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.mddocs/architecture.mddocs/contributing.mdAGENTS.md
Local quality workflow:
bun run format
bun run lint
bun test
bun run buildCurrent scope
- Bun is required to run the CLI
- v1 targets macOS and Linux shell behavior
- Windows support is intentionally deferred
