robin_cli_tool 1.3.0

A CLI tool to run scripts for any project
Documentation

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:

{
    "scripts": {
        "build": "cargo build --release",
        "test": "cargo test",
        "deploy": "fastlane {{env=[staging,production]}}"
    }
}

Then run any task by name:

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

# From crates.io
cargo install robin_cli_tool

# From source
cargo install --path .

Usage

Initialize a new project

robin init

This creates a .robin.json file in your current directory with some template scripts.

Using templates

# 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

robin --list

Interactive mode

robin --interactive  # or -i

Add a new command

robin add "deploy" "fastlane deliver --submit-to-review"

Remove or rename a command

robin remove "deploy"          # or: robin rm "deploy"
robin rename "deploy" "ship"   # rename a task, keeping its definition

Run a command

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

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

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

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:

{
    "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:

{
    "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:

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:

{
    "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:

{
    "$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:

{
    "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:

{
    "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:

{
    "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:

{
    "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:

{
    "scripts": {
        "deploy": "fastlane {{platform}} {{env}}"
    }
}

Then run:

robin deploy --platform=ios --env=staging

Default Values

You can specify default values for variables using {{variable=default}} syntax:

{
    "scripts": {
        "build": "echo \"Building {{mode=debug}}\"",
        "deploy": "echo \"Deploying to {{env=staging}} with version {{version=latest}}\""
    }
}

Using default values:

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:

{
    "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:

# 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:

{
    "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:

{
    "scripts": {
        "serve": "echo \"starting on port ${PORT:-8080}\"",
        "deploy": "kubectl apply -n ${NAMESPACE:-default} -f deploy.yaml"
    }
}
robin serve              # PORT unset -> "starting on port 8080"
PORT=9000 robin serve    # PORT set   -> "starting on port 9000"

Two forms are supported (defaults only):

Syntax Behavior
${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:

# .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:

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:

🔍 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:

robin doctor-update

This will update:

  • Rust (via rustup)
  • Flutter
  • Fastlane (via gem)
  • Global npm packages
  • CocoaPods repositories

Update Notifications

Robin checks crates.io 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