<h1 align="center">robin</h1>
<p align="center">Your own customizable <b>CLI</b> tool</p>
<p align="center">
<a href="https://crates.io/crates/robin_cli_tool"><img alt="Crates.io Version" src="https://img.shields.io/crates/v/robin_cli_tool"></a>
<a href="https://github.com/cesarferreira/robin/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
<img alt="Crates.io Total Downloads" src="https://img.shields.io/crates/d/robin_cli_tool">
</p>
<p align="center">
<img src="media/terminal_ss4.png" width="100%" />
</p>
## Reason
> Maintaining a simple JSON file with all the available tasks allows for easy customization of deployment, release, cleaning, and other project-specific actions. This ensures that everyone on the team can use, edit, and add tasks on a project level.
## Quick look
Drop a `.robin.json` in your project:
```json
{
"scripts": {
"build": "cargo build --release",
"test": "cargo test",
"deploy": "fastlane {{env=[staging,production]}}"
}
}
```
Then run any task by name:
```bash
robin build
robin test
robin deploy --env=staging
```
That's it. Read on for templates, variables, sequences, and more.
## Features
- Define and run project-specific scripts via `.robin.json`
- Support for both single commands and command sequences
- Interactive mode with fuzzy search
- List all available commands
- Add new commands easily
- Cross-platform support
- Template initialization for different project types
- Variable substitution with default values
- Enum validation for variables
- Environment variable substitution with defaults (`${VAR:-default}`)
- Automatic `.env` file loading
- Optional per-task descriptions (shown in `--list` and interactive mode)
- Reference other tasks from a sequence with `@task`
- Optional desktop notification on completion with `--notify`
## Installation
```bash
# From crates.io
cargo install robin_cli_tool
# From source
cargo install --path .
```
## Usage
### Initialize a new project
```bash
robin init
```
This creates a `.robin.json` file in your current directory with some template scripts.
### Using templates
```bash
# Initialize with a specific template
robin init --template android # Android project template
robin init --template ios # iOS project template
robin init --template flutter # Flutter project template
robin init --template rails # Ruby on Rails project template
robin init --template node # Node.js/TypeScript project template
robin init --template nextjs # Next.js project template
robin init --template python # Python project template
robin init --template rust # Rust project template
robin init --template go # Go project template
```
Each template comes with a curated set of useful commands for that specific platform or framework. For example:
- **Android**: Gradle commands, testing, linting (ktlint), and deployment
- **iOS**: Xcode build, CocoaPods, testing, SwiftLint, and Fastlane commands
- **Flutter**: Build, test, dependency management, and platform-specific commands
- **Rails**: Server, console, database tasks, testing, and code generation
- **Node.js**: Development, testing (Jest), TypeScript, linting (ESLint), and formatting (Prettier)
- **Python**: Virtual env, testing (pytest), linting (flake8), formatting (black), and type checking (mypy)
- **Rust**: Cargo commands for building, testing, linting (clippy), formatting, and documentation
- **Go**: Build, test, linting (golangci-lint), formatting, and dependency management
If a `.robin.json` file already exists, you'll be prompted to confirm before overriding it.
### List all commands
```bash
robin --list
```
### Interactive mode
```bash
robin --interactive # or -i
```
### Add a new command
```bash
robin add "deploy" "fastlane deliver --submit-to-review"
```
### Remove or rename a command
```bash
robin remove "deploy" # or: robin rm "deploy"
robin rename "deploy" "ship" # rename a task, keeping its definition
```
### Run a command
```bash
robin deploy staging
robin release beta
```
Robin looks for `.robin.json` in the current directory and walks up the parent
directories until it finds one — so you can run commands from anywhere inside
your project, just like `git` or `cargo`. (`robin init` always writes to the
current directory.)
### Preview a command with `--dry-run`
```bash
robin deploy --dry-run
```
Prints the fully-resolved commands — with `@task` references expanded and all
variables substituted — without executing anything. The flag works before or
after the task name.
### Run a task in another directory with `--cwd`
```bash
robin test --cwd ./services/api
```
Runs the task's commands in the given directory instead of the current one —
handy in monorepos where a root-level task should act on a subproject. Like the
other flags, it also accepts the `--cwd=DIR` form after the task name.
### Get notified when a task finishes with `--notify`
```bash
robin deploy --notify
```
Sends a desktop notification when the task completes, reporting success or
failure along with the total execution time — useful for long-running builds or
deployments you don't want to babysit. Like the other flags, it works before or
after the task name. Notifications are off by default and only fire when
`--notify` is passed.
## Configuration
The `.robin.json` file supports both single commands and command sequences:
```json
{
"scripts": {
"clean": "rm -rf build/",
"deploy": "echo 'ruby deploy tool --{{env=[staging,production]}}'",
"prep-and-deploy": [
"robin clean",
"robin build",
"robin deploy --env=production"
],
"full-release": [
"flutter clean",
"flutter pub get",
"flutter build ios",
"cd ios && fastlane beta"
]
}
}
```
When using command sequences (arrays):
- Commands are executed in order
- Each command is echoed (prefixed with `▶`) as it runs, so you can follow along
- If any command fails, the sequence stops
- Environment variables and working directory are preserved between commands
- With `--notify`, a desktop notification reports the total execution time when the sequence finishes
### Task descriptions
Any task can carry a description by switching to the object form. The command
itself goes under `cmd` (a string *or* an array), and `desc` is shown in
`robin --list` and the interactive picker:
```json
{
"scripts": {
"build": "cargo build",
"deploy": {
"cmd": ["cargo build --release", "scp target/release/app server:/srv"],
"desc": "Build a release binary and copy it to the server"
}
}
}
```
The plain string and array forms keep working — descriptions are entirely
optional. To add the `desc` scaffolding to every task in an existing file, run:
```bash
robin migrate
```
This rewrites `.robin.json` so each task uses the `{ "cmd": ..., "desc": "" }`
form, ready for you to fill in the descriptions.
### Referencing other tasks
Inside a command sequence, an entry that starts with `@` runs another task by
name instead of a shell command. References are expanded recursively, so tasks
compose without duplication:
```json
{
"scripts": {
"clean": "rm -rf build/",
"build": ["@clean", "cargo build --release"],
"ship": ["@build", "scp target/release/app server:/srv"]
}
}
```
Running `robin ship` executes `clean`, then `build`, then the deploy step.
Reference cycles are detected and reported as an error.
### Editor autocomplete (JSON Schema)
A JSON Schema for `.robin.json` is published at:
```
https://raw.githubusercontent.com/cesarferreira/robin/refs/heads/main/schema/robin.schema.json
```
`robin init` and `robin migrate` add a `$schema` key pointing at it, so editors
like VS Code offer autocomplete and validation out of the box. You can also add
it to any existing file yourself:
```json
{
"$schema": "https://raw.githubusercontent.com/cesarferreira/robin/refs/heads/main/schema/robin.schema.json",
"scripts": {
"build": "cargo build"
}
}
```
The `$schema` key is preserved when robin rewrites the file (via `add`,
`remove`, `rename`, or `migrate`).
## External Configuration
Robin supports including external configuration files, which is particularly useful for monorepos or sharing common scripts across projects:
```json
{
"include": [
"../common/robin.base.json",
"./team-specific.json"
],
"scripts": {
"local-dev": "npm run dev",
"test": "npm run test"
}
}
```
### Monorepo Example
Here's a typical monorepo structure using shared scripts:
```
monorepo/
├── common/
│ └── robin.base.json # Shared scripts for all projects
├── frontend/
│ ├── .robin.json # Frontend-specific scripts
│ └── package.json
├── backend/
│ ├── .robin.json # Backend-specific scripts
│ └── package.json
└── mobile/
├── .robin.json # Mobile-specific scripts
└── pubspec.yaml
```
`common/robin.base.json`:
```json
{
"scripts": {
"lint": "eslint .",
"format": "prettier --write .",
"docker:up": "docker-compose up -d",
"docker:down": "docker-compose down",
"ci:test": [
"npm ci",
"npm run test"
]
}
}
```
`frontend/.robin.json`:
```json
{
"include": ["../common/robin.base.json"],
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"deploy:staging": [
"robin docker:down",
"robin build",
"robin docker:up"
]
}
}
```
`mobile/.robin.json`:
```json
{
"include": ["../common/robin.base.json"],
"scripts": {
"dev": "flutter run",
"build:android": "flutter build apk",
"build:ios": "flutter build ios",
"deploy:beta": [
"robin build:{{platform=[ios,android]}}",
"fastlane {{platform}} beta"
]
}
}
```
Scripts from included files are merged with local scripts, where local scripts take precedence. This allows you to:
- Share common development workflows across projects
- Maintain consistent CI/CD scripts
- Override shared scripts when needed
- Keep project-specific scripts separate from shared ones
## Variable Substitution
### Basic Variables
Use `{{variable}}` in your scripts and pass them as `--variable=XXX` when running the command:
```json
{
"scripts": {
"deploy": "fastlane {{platform}} {{env}}"
}
}
```
Then run:
```bash
robin deploy --platform=ios --env=staging
```
### Default Values
You can specify default values for variables using `{{variable=default}}` syntax:
```json
{
"scripts": {
"build": "echo \"Building {{mode=debug}}\"",
"deploy": "echo \"Deploying to {{env=staging}} with version {{version=latest}}\""
}
}
```
Using default values:
```bash
robin build # Will use default: debug
robin deploy # Will use defaults: staging and latest
# Override defaults:
robin build --mode=release # Will use: release
robin deploy --env=prod --version=1.0.0 # Will use: prod and 1.0.0
```
### Enum Validation
You can restrict variable values to a specific set using `{{variable=[value1, value2, ...]}}` syntax:
```json
{
"scripts": {
"deploy": "echo \"Deploying to {{env=[staging, prod]}}\"",
"build": "cargo build --{{mode=[debug, release]}}",
"deploy:app": "fastlane {{platform=[ios, android]}} {{env=[dev, staging, prod]}} --track={{track=[alpha, beta, production]}}"
}
}
```
Using enum validation:
```bash
# Simple validation
robin deploy --env=staging # Works: 'staging' is allowed
robin deploy --env=prod # Works: 'prod' is allowed
robin deploy --env=dev # Fails: only 'staging' or 'prod' are allowed
# Build modes
robin build --mode=debug # Works: 'debug' is allowed
robin build --mode=release # Works: 'release' is allowed
robin build --mode=test # Fails: only 'debug' or 'release' are allowed
# Multiple validations
robin deploy:app \
--platform=ios \
--env=staging \
--track=beta # Works: all values are allowed
robin deploy:app \
--platform=web \ # Fails: 'web' is not in [ios, android]
--env=staging \
--track=beta
```
Variables work in both single commands and command sequences:
```json
{
"scripts": {
"deploy-sequence": [
"flutter clean",
"flutter build {{platform=[ios,android]}}",
"fastlane {{platform}} beta"
]
}
}
```
### Environment Variables with Defaults
In addition to the `{{...}}` syntax (which reads from `--variable=` arguments), you can
read values from the **environment** using Docker Compose-style `${VAR:-default}` syntax:
```json
{
"scripts": {
"serve": "echo \"starting on port ${PORT:-8080}\"",
"deploy": "kubectl apply -n ${NAMESPACE:-default} -f deploy.yaml"
}
}
```
```bash
robin serve # PORT unset -> "starting on port 8080"
PORT=9000 robin serve # PORT set -> "starting on port 9000"
```
Two forms are supported (defaults only):
| `${VAR:-default}` | Use `$VAR` if it is set **and non-empty**, otherwise `default`. |
| `${VAR-default}` | Use `$VAR` if it is **set** (even if empty), otherwise `default`. |
A bare `${VAR}` (with no default) is left untouched and expanded by the shell at run
time, exactly as before.
### `.env` files
If a `.env` file sits next to your `.robin.json`, robin loads it automatically
before running a task, so both the `${VAR:-default}` substitution above and your
shell commands can use those values:
```bash
# .env (next to .robin.json)
PORT=9000
NAMESPACE=production
```
Variables already present in the environment take precedence over the file, and
loading can be disabled by setting `ROBIN_NO_DOTENV`.
## Development Environment
### Doctor Command
The `doctor` command helps verify your development environment is properly set up:
```bash
robin doctor
```
This will check:
- 📦 Required Tools
- Cargo and Rust
- Ruby and Fastlane
- Flutter
- Node.js and npm
- 🔧 Environment Variables
- ANDROID_HOME
- JAVA_HOME
- FLUTTER_ROOT
- 📱 Platform Tools
- Android Debug Bridge (adb)
- Xcode Command Line Tools
- CocoaPods
- 🔐 Git Configuration
- user.name
- user.email
Example output:
```bash
🔍 Checking development environment...
📦 Required Tools:
✅ Cargo: cargo 1.75.0
✅ Rust: rustc 1.75.0
✅ Ruby: ruby 3.2.2
✅ Fastlane: fastlane 2.217.0
❌ Flutter not found
✅ Node.js: v20.10.0
✅ npm: 10.2.3
🔧 Environment Variables:
✅ ANDROID_HOME is set
✅ JAVA_HOME is set
❌ FLUTTER_ROOT is not set
📱 Platform Tools:
✅ Android Debug Bridge (adb): Android Debug Bridge version 1.0.41
✅ Xcode Command Line Tools: installed
✅ CocoaPods: 1.14.3
🔐 Git Configuration:
✅ Git user.name is set
✅ Git user.email is set
```
### Update Development Tools
To update all development tools to their latest versions:
```bash
robin doctor-update
```
This will update:
- Rust (via rustup)
- Flutter
- Fastlane (via gem)
- Global npm packages
- CocoaPods repositories
## Update Notifications
Robin checks [crates.io](https://crates.io/crates/robin_cli_tool) for newer
releases and prints a short notice when your installed version is out of date:
```
➜ A new version of robin is available: 1.5.0 (you have 1.0.2).
Update with: cargo install robin_cli_tool
```
The check is throttled to at most once per day (the result is cached under your
OS cache directory), so it never slows down day-to-day usage. To disable it
entirely, set the `ROBIN_NO_UPDATE_CHECK` environment variable.
## License
MIT © [Cesar Ferreira](http://cesarferreira.com)