@yaos-git/run-ctx
v126.0.4
Published
Context-aware command alias CLI — run the right command based on cwd, files, and env vars
Maintainers
Readme
Table of Contents
Getting Started
Configuration
TUI Editor
Development
Overview
run-ctx is a globally-installed CLI that aliases commands based on context -- your current working directory, the files present, and environment variables.
Define a single alias like dev, and run-ctx resolves it to the right command for whatever project you are in:
# In a Node project (has package.json):
run-ctx dev # --> npm run dev
# In a PHP project (has composer.json):
run-ctx dev # --> composer serve
# In a Rust project (has Cargo.toml):
run-ctx dev # --> cargo watch -x runNo more remembering which command goes with which project.
What Makes This Project Unique
- Context-Aware: Automatically detects project type via files, cwd, and env vars
- Specificity Scoring: Multi-condition rules with CSS-style cascade resolution
- TUI Editor: Interactive terminal UI for managing aliases and rules
- Zero Config Per-Project: One global config works across all your projects
Installation
# Install globally from npm
npm install -g run-ctx
# Or install as a dev dependency
npm install -D run-ctxFrom Source
# Clone the repository
git clone https://github.com/YAOSGit/run-ctx.git
cd run-ctx
# Install dependencies
npm install
# Build the project
npm run build
# Link globally (optional)
npm linkQuick Start
- Generate a starter configuration file automatically:
run-ctx --initNote: This automatically creates
~/.config/run-ctx/config.jsonpopulated with common smart aliases likedev,build,test,lint, andstartconfigured for popular languages and frameworks.
- Run it:
cd ~/my-node-project
run-ctx dev
# Executes: npm run devConfig Format
The config file lives at ~/.config/run-ctx/config.json (or $XDG_CONFIG_HOME/run-ctx/config.json if set).
Schema
{
"version": 2,
"aliases": {
"<alias-name>": {
"description": "Optional human-readable description",
"rules": [
{
"match": {
"file": "<glob pattern>",
"cwd": "<regex pattern>",
"env": "<VAR_NAME>"
},
"command": "<shell command to run>",
"shell": false
}
],
"fallback": "<optional command when no rules match>",
"shell": false
}
}
}Example
{
"aliases": {
"dev": {
"description": "Start development server",
"rules": [
{
"match": { "file": "package.json", "cwd": "frontend" },
"command": "npm run dev"
},
{
"match": { "file": "composer.json" },
"command": "composer serve"
}
],
"fallback": "echo 'No dev server configured for this project'"
}
}
}Match Conditions
Each rule has a match object with one or more conditions. All specified conditions must be satisfied for the rule to match.
| Condition | Type | What it checks | Example |
|-----------|----------|-------------------------------------------------------|----------------------------|
| file | Glob | Whether a file matching the pattern exists in cwd | "package.json", "*.go" |
| cwd | Regex | Whether the current directory path matches | "frontend", "/api$" |
| env | Var name | Whether the environment variable is set and non-empty | "CI", "DOCKER" |
Specificity Scoring
Each condition in a rule that is defined and satisfied scores 1 point. The rule with the highest total score wins.
- A rule with
file+cwd+envall matching scores 3. - A rule with only
filematching scores 1. - When two rules have the same score, the later rule in the array wins (CSS-style cascade).
- If no rules match and a
fallbackis defined, the fallback command runs.
CLI Usage
run-ctx <alias> [args...] Run alias, pass through additional args
run-ctx --list, -l Show all aliases and matched commands for current context
run-ctx --dry-run <alias> Show what command would run without executing
run-ctx --edit, -e Launch the TUI editor (run-ctx-editor)
run-ctx --completions <shell> Generate shell completion script (bash, zsh, fish)
run-ctx --shell Run command in shell (allows pipe, redirect, &&)
run-ctx --verbose, -V Show detailed rule evaluation logs
run-ctx --help, -h Show help message
run-ctx --version, -v Show version informationExamples
# Run the "test" alias with extra args
run-ctx test --coverage
# See what every alias resolves to in the current directory
run-ctx --list
# Preview the resolved command without executing
run-ctx --dry-run build
# Open the interactive config editor
run-ctx --edit
# Configure bash tab-completion for rc/run-ctx
eval "$(rc --completions bash)"TUI Editor
Launch with run-ctx-editor or run-ctx --edit. The editor provides three screens for managing your config interactively.
AliasList (home screen)
| Key | Action |
|-----------|-----------------------|
| Up / Down | Navigate aliases |
| Enter | Edit selected alias |
| n | Create new alias |
| d | Delete selected alias |
| q / Esc | Quit editor |
RuleEditor (alias detail)
| Key | Action |
|-----------|----------------------|
| Up / Down | Navigate rules |
| Enter | Edit selected rule |
| n | Add new rule |
| d | Delete selected rule |
| j | Move rule down |
| J | Move rule up |
| q / Esc | Back to alias list |
RuleDetail (rule fields)
| Key | Action | |-----------|-------------------------------------------| | Up / Down | Navigate fields (command, file, cwd, env) | | Enter | Edit field value | | Esc | Save and go back |
Config Example
A realistic config for a polyglot developer:
{
"aliases": {
"dev": {
"description": "Start development server",
"rules": [
{ "match": { "file": "package.json" }, "command": "npm run dev" },
{ "match": { "file": "composer.json" }, "command": "php artisan serve" },
{ "match": { "file": "Cargo.toml" }, "command": "cargo watch -x run" },
{ "match": { "file": "go.mod" }, "command": "go run ." }
]
},
"test": {
"description": "Run tests",
"rules": [
{ "match": { "file": "package.json" }, "command": "npm test" },
{ "match": { "file": "composer.json" }, "command": "php artisan test" },
{ "match": { "file": "Cargo.toml" }, "command": "cargo test" },
{ "match": { "file": "go.mod" }, "command": "go test ./..." }
]
},
"build": {
"description": "Build project",
"rules": [
{ "match": { "file": "package.json" }, "command": "npm run build" },
{ "match": { "file": "Cargo.toml" }, "command": "cargo build --release" },
{ "match": { "file": "go.mod" }, "command": "go build -o bin/app ." }
],
"fallback": "echo 'No build configured'"
},
"lint": {
"description": "Lint code",
"rules": [
{ "match": { "file": "biome.json" }, "command": "npx biome check ." },
{ "match": { "file": ".eslintrc*" }, "command": "npx eslint ." },
{ "match": { "file": "Cargo.toml" }, "command": "cargo clippy" },
{ "match": { "file": "go.mod" }, "command": "golangci-lint run" }
]
},
"deploy": {
"description": "Deploy (CI only)",
"rules": [
{
"match": { "file": "package.json", "env": "CI" },
"command": "npm run deploy"
}
]
}
}
}Available Scripts
Development Scripts
| Script | Description |
|--------|-------------|
| npm run dev | Run tests in watch mode |
| npm run dev:typescript | Run TypeScript type checking in watch mode |
Build Scripts
| Script | Description |
|--------|-------------|
| npm run build | Bundle the CLI with esbuild |
Lint Scripts
| Script | Description |
|--------|-------------|
| npm run lint | Run type checking, linting, formatting, and audit |
| npm run lint:check | Check code for linting issues with Biome |
| npm run lint:fix | Check and fix linting issues with Biome |
| npm run lint:format | Format all files with Biome |
| npm run lint:types | Run TypeScript type checking only |
| npm run lint:audit | Run npm audit |
Testing Scripts
| Script | Description |
|--------|-------------|
| npm test | Run all tests (unit, react) |
| npm run test:unit | Run unit tests |
| npm run test:react | Run React component tests |
Tech Stack
Core
- React 19 - UI component library
- Ink 6 - React for CLIs
- TypeScript 5 - Type-safe JavaScript
- picomatch - Blazing fast glob matching
- re2 - Linear-time regex engine (ReDoS protected)
- omelette - Shell tab-completion engine
Build & Development
UI Components
Project Structure
run-ctx/
├── src/
│ ├── app/ # Application entry points
│ │ ├── cli.ts # CLI entry point
│ │ ├── editor-cli.tsx # TUI editor entry point
│ │ └── app.tsx # Main application component
│ ├── components/ # React components
│ │ ├── AliasList/ # Home screen - list all aliases
│ │ ├── RuleEditor/ # Edit rules for an alias
│ │ └── RuleDetail/ # Edit individual rule fields
│ ├── types/ # TypeScript type definitions
│ │ ├── Alias/ # Alias type definitions
│ │ ├── Color/ # Color constants and types
│ │ ├── Config/ # Config type definitions
│ │ └── Rule/ # Rule type definitions
│ └── utils/ # Utility functions
│ ├── config/ # Load/save config from ~/.config/run-ctx
│ ├── executor/ # Execute resolved commands
│ ├── matcher/ # Match rules based on conditions
│ └── resolver/ # Resolve alias to command
├── docs/ # Documentation and plans
├── dist/ # Built output
├── biome.json # Biome configuration
├── tsconfig.json # TypeScript configuration
├── tsconfig.app.json # App TypeScript configuration
├── vitest.unit.config.ts # Unit test configuration
├── vitest.react.config.ts # React test configuration
├── esbuild.config.js # esbuild bundler configuration
└── package.jsonVersioning
This project uses a custom versioning scheme: MAJORYY.MINOR.PATCH
| Part | Description | Example |
|------|-------------|---------|
| MAJOR | Major version number | 1 |
| YY | Year (last 2 digits) | 26 for 2026 |
| MINOR | Minor version | 0 |
| PATCH | Patch version | 0 |
Example: 126.0.0 = Major version 1, released in 2026, minor 0, patch 0
This format allows you to quickly identify both the major version and the year of release at a glance.
Style Guide
Conventions for contributing to this project. All rules are enforced by code review; Biome handles formatting and lint.
Exports
- Named exports only — no
export default. Every module usesexport function,export const, orexport type. import type— always useimport typefor type-only imports..jsextensions — all relative imports use explicit.jsextensions (ESM requirement).
File Structure
src/
├── app/ # Entry points, root component, providers wrapper
├── components/ # React components (PascalCase directories)
│ └── MyComponent/
│ ├── index.tsx
│ ├── MyComponent.types.ts
│ └── MyComponent.test.tsx
├── hooks/ # Custom hooks (camelCase directories)
│ └── useMyHook/
│ ├── index.ts
│ ├── useMyHook.types.ts
│ └── useMyHook.test.tsx
├── providers/ # React context providers (PascalCase directories)
│ └── MyProvider/
│ ├── index.tsx
│ ├── MyProvider.types.ts
│ └── MyProvider.test.tsx
├── types/ # Shared type definitions (PascalCase directories)
│ └── MyType/
│ ├── index.ts
│ └── MyType.test-d.ts
└── utils/ # Pure utility functions (camelCase directories)
└── myUtil/
├── index.ts
└── myUtil.test.tsComponents & Providers
- Components use
functiondeclarations:export function MyComponent(props: MyComponentProps) {} - Providers use
React.FCarrow syntax:export const MyProvider: React.FC<Props> = ({ children }) => {} - Props are defined in a co-located
.types.tsfile using theinterfacekeyword. - Components receive data via props — never read
process.stdoutor global state directly.
Types
- Use
typefor data shapes and unions. Useinterfacefor component props. - Shared types live in
src/types/TypeName/index.tswith a co-locatedTypeName.test-d.ts. - Local types live in co-located
.types.tsfiles — never inline in implementation files. - No duplicate type definitions — import from the canonical source.
- Runtime constants must not live in
src/types/— use.consts.tsfiles.
Constants
- Named constants go in
.consts.tsfiles (e.g.,useMyHook.consts.ts). - No magic numbers in implementation files — extract to named constants.
Testing
- Every module has a co-located test file.
- Components:
ComponentName.test.tsx - Hooks:
hookName.test.tsx - Utils:
utilName.test.ts - Types:
TypeName.test-d.ts(type-level tests usingexpectTypeOf/assertType)
License
ISC
