Karahi is a command runner for your projects. Define commands in a simple JSON file and run them from the terminal — either directly via CLI flags or through an interactive TUI with fuzzy search, parameter forms, and styled output.
Prerequisites: Go 1.25+
# Clone and build
git clone https://github.com/eshanclio/karahi.git
cd karahi
go build -o karahi .
# Move to a directory on your PATH
mv karahi /usr/local/bin/Or run directly without installing:
go run .- Create a
karahi.jsonin your project root:
{
"commands": [
{
"name": "greet",
"description": "Greet someone by name",
"params": [
{
"name": "name",
"description": "The person to greet",
"type": "input",
"validation": { "required": true }
}
],
"exec": [
"echo 'Hello, {{name}}!'"
]
}
]
}- Run it:
# CLI mode
karahi greet --name Alice
# TUI mode
karahiRun any command directly with flags:
karahi <command> [--param value ...]Examples:
karahi greet --name Alice
karahi deploy --environment staging --version v1.2.0
karahi statusFlags accept both --param value and --param=value syntax.
karahi help # List all available commands
karahi help <command> # Show details for a specific commandThe help output shows each command's description, parameters (with types and whether they're required), and the exec steps that will run.
Launch the interactive terminal UI:
karahi # TUI mode (default)
karahi tui # TUI mode (explicit)| Key | Action |
|---|---|
/ |
Open the command list |
enter |
Select a command / submit the form |
esc |
Go back / cancel |
? |
Show help |
q |
Quit (when not typing) |
ctrl+c |
Force quit |
up / k |
Scroll up |
down / j |
Scroll down |
pgup / pgdown |
Page up / down |
ctrl+u / ctrl+d |
Half page up / down |
home / end |
Scroll to top / bottom |
- Idle — press
/to open the command list. - Command Select — type to filter commands with fuzzy search, then press
enterto select one. - Form Input — fill in the command's parameters (skipped if the command has no params).
- Executing — the command runs with a spinner and streamed output.
Press esc at any point to go back to the idle state.
Commands are defined in karahi.json. Karahi checks two locations:
| Location | Path | Priority |
|---|---|---|
| Project | ./karahi.json |
Higher (overrides global) |
| Global | ~/.karahi/karahi.json |
Lower (fallback) |
If both files define a command with the same name, the project version is used. Global commands that don't conflict are still available.
{
"commands": [
{
"name": "deploy",
"description": "Deploy to an environment",
"params": [
{
"name": "environment",
"description": "Target environment",
"type": "choice",
"options": ["staging", "production"],
"validation": { "required": true }
},
{
"name": "version",
"description": "Version tag",
"type": "input",
"validation": {
"required": true,
"min_length": 2,
"max_length": 50,
"pattern": "^v\\d+\\.\\d+\\.\\d+$"
}
}
],
"exec": [
"echo 'Deploying {{version}} to {{environment}}...'",
"echo 'Done!'"
]
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | The command name (used as the CLI subcommand). |
description |
string | Yes | A short description shown in help and the TUI list. |
params |
array | No | Parameters the command accepts. |
exec |
array | Yes | Shell commands to run sequentially via sh -c. |
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Parameter name (used as --name in CLI). |
description |
string | Yes | Shown in help text and TUI form labels. |
type |
string | Yes | "input" (free text) or "choice" (select from options). |
options |
array | For choice |
The allowed values when type is "choice". |
validation |
object | No | Validation rules for this parameter. |
All validation fields are optional. When multiple rules are set, they are all evaluated in sequence.
| Rule | Type | Description |
|---|---|---|
required |
bool | Value must be non-empty. |
min_length |
int | Minimum character count. |
max_length |
int | Maximum character count. |
pattern |
string | A regex the value must match. |
one_of |
array | Value must be one of the listed strings. |
Use {{param_name}} in your exec strings to reference parameter values:
{
"exec": ["echo 'Deploying {{version}} to {{environment}}'"]
}Commands don't need parameters. Omit the params field entirely:
{
"name": "status",
"description": "Show project status",
"exec": ["echo 'All good'", "date"]
}main.go Entry point: loads config, routes to CLI or TUI
karahi.json Example project-level command definitions
internal/
domain/
config.go Config loading and merging (project + global)
config_test.go Tests for config loading, merging, FindCommand
command.go Data types: Command, Param, ValidationRule
cli/
cli.go CLI runner: flag parsing, validation, execution
cli_test.go Tests for flag parsing, validation, help, Run
executor/
executor.go Shell execution and parameter substitution
executor_test.go Tests for Substitute, Run, RunStructured, RunCapture
tui/
model.go Bubble Tea model and state machine
commands.go Filterable command list (bubbles/list)
form.go Dynamic form builder (huh)
output.go Output viewport with spinner
keys.go Key bindings
tui_test.go Tests for helpText, collectValues, commandItem, commandList
validation/
validator.go Composable validator registry
rules.go Built-in validation rules
rules_test.go Tests for all rule factories and BuildValidators
go build . # Produces ./karahigo run . # Run directly
./karahi # Run the built binaryRun the full test suite:
go test ./...Run tests for a specific package:
go test ./internal/cli
go test ./internal/executor
go test ./internal/domain
go test ./internal/validation
go test ./internal/tuiRun a single test by name:
go test ./internal/cli -run TestParseFlags
go test ./internal/validation -run TestBuildValidatorsRun tests with verbose output:
go test -v ./...All non-TUI-interactive packages have tests:
| Package | What's Tested |
|---|---|
internal/domain |
Config file loading, JSON parsing, command merging (project overrides global), FindCommand lookup, LoadConfig integration |
internal/cli |
Flag parsing (--key value and --key=value), parameter validation, help / help <command> output, end-to-end Run |
internal/executor |
Substitute placeholder replacement, RunStructured / Run / RunCapture execution, error propagation and stop-on-first-error behavior |
internal/validation |
Each rule factory (required, minLength, maxLength, pattern, oneOf), BuildValidators composition, nil-rule passthrough, UTF-8 rune counting |
internal/tui |
helpText rendering, collectValues pointer dereferencing, commandItem interface methods, commandList construction (including synthetic help entry), show/hide visibility |
When writing new tests, follow these conventions:
- Use
t.Parallel()where possible. Avoid it only when test state is process-global (e.g.,os.Chdir). - Use
t.TempDir()for temporary directories. - Use table-driven tests for functions with many input/output cases.
- Use subtests (
t.Run) to group related assertions.
| Package | Role |
|---|---|
| bubbletea v2 | TUI framework (Elm architecture) |
| bubbles v2 | Reusable TUI components (list, viewport, spinner) |
| huh v2 | Interactive form builder |
| lipgloss v2 | Terminal styling |
- Imports: group stdlib, then external, then internal packages.
- Naming: PascalCase exported, camelCase unexported.
- Errors: return explicitly, wrap with
fmt.Errorf("...: %w", err). - JSON tags:
snake_casewithomitemptywhere appropriate. - Pure Go — no CGO, no C dependencies.
Karahi follows a clean separation between its three main layers:
-
Domain (
internal/domain) holds all shared data types (Command,Param,ValidationRule,ResolvedCommand) and config loading logic. It has no dependencies on the CLI or TUI layers. -
Executor (
internal/executor) handles shell execution. It substitutes{{param}}placeholders and runs each exec step sequentially viash -c, stopping on the first error. Three execution modes are available:Run(streamed output to a writer),RunStructured(structured results for the TUI), andRunCapture(captures output as a string while also writing to a writer). -
Validation (
internal/validation) uses a registry pattern. Each rule is aRuleFactorythat returns aValidateFunc.BuildValidatorscomposes all applicable rules into a single validation function. New rules can be added viaRegister(). -
CLI (
internal/cli) parses flags, validates parameters, and runs commands via the executor. It also handles thehelpsubcommand. -
TUI (
internal/tui) implements the interactive UI using Bubble Tea's model/update/view pattern with a four-state machine: idle, command select, form input, and executing.
- Create a factory function in
internal/validation/rules.go. - Register it with
Register()in theinit()function. - Add the corresponding field to
ValidationRuleininternal/domain/command.go.
- Add the type string to
ParamTypeininternal/domain/command.go. - Handle it in
internal/tui/form.go(buildForm). - Handle it in
internal/cli/cli.go(parseFlags) if CLI behavior differs.
- Add the state constant in
internal/tui/model.go. - Add a key handler function (
handleXxxKey). - Wire it into
UpdateandView. - Update
recalcLayoutif it needs custom sizing.
See LICENSE for details.