
Cli Just
- 320 installs
- 69 repo stars
- Updated August 4, 2026
- paulrberg/agent-skills
cli-just is a Claude Code agent skill that teaches AI coding agents to author and maintain justfile recipes for the Just command runner (v1.55.0) so developers can standardize build, test, deploy, and codegen tasks.
About
cli-just is an agent skill from paulrberg/agent-skills that documents Just—the make-inspired command runner—for AI-assisted justfile authoring. The skill targets Just v1.55.0 and bundles 5 reference guides (settings, recipes, syntax, patterns, inline-scripts) plus 2 example templates (devkit.just, standalone.just). It covers recipe attributes, v1.46+ [arg()] CLI flags, built-in color constants, module imports, check/write patterns, and unstable features like user-defined functions and list values. Developers reach for cli-just when adding or refactoring a justfile, onboarding agents to existing recipes, or adopting newer Just features agents may not know from training data alone.
- justfile syntax for recipes and parameters
- Cross-platform command aliases and dependencies
- Integration with build, test, and lint pipelines
- Safer alternative to brittle Makefile targets
Cli Just by the numbers
- 320 all-time installs (skills.sh)
- +7 installs in the week ending Aug 4, 2026 (Skillselion tracking)
- Ranked #167 of 550 CLI & Terminal skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/paulrberg/agent-skills --skill cli-justAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 320 |
|---|---|
| repo stars | ★ 69 |
| Last updated | August 4, 2026 |
| Repository | paulrberg/agent-skills ↗ |
How do you write justfile recipes with modern Just syntax?
Author and maintain justfile recipes so agents and developers run repeatable build, test, deploy, and codegen commands with a simple cross-platform task runner.
Who is it for?
Developers who standardize project automation with Just and want coding agents to produce idiomatic, version-aware justfiles instead of ad-hoc shell scripts.
Skip if: Teams committed to Makefile, npm scripts, or another task runner who do not plan to adopt or maintain a justfile.
When should I use this skill?
Trigger when the user asks to create, edit, or debug a justfile, add Just recipes, configure Just modules or settings, or run build/test/deploy tasks through `just`.
What you get
Structured justfiles with section headers, dependency declarations, parameterized recipes, module imports, and documented build/test/deploy/check recipes ready for `just` execution.
- justfile
- module justfiles
- documented recipe catalog
By the numbers
- Targets Just v1.55.0 (released 2026-06-29)
- Bundles 5 reference docs and 2 example justfile templates
- Documents 18 recipe attributes in the quick-reference table
Files
Just Command Runner
Overview
Expert guidance for Just, a command runner with syntax inspired by make. Use this skill for creating justfiles, writing recipes, configuring settings, and implementing task automation workflows.
Targets just v1.53.0 (latest stable, released 2026-06-16). Features are tagged with the version that introduced them (e.g. v1.51.0+); a tag newer than your installed just --version means you must upgrade to use it. List features (set lists) and other gated capabilities additionally require set unstable.
Key capabilities:
- Create and organize justfiles with proper structure
- Write recipes with attributes, dependencies, and parameters
- Configure settings for shell, modules, and imports
- Use built-in constants for terminal formatting
- Implement check/write patterns for code quality tools
Quick Reference
Essential Settings
set allow-duplicate-recipes # Allow recipes to override imported ones
set allow-duplicate-variables # Allow variables to override imported ones
set shell := ["bash", "-euo", "pipefail", "-c"] # Strict bash with error handling
set unstable # Enable unstable features (user-defined functions, eager keyword)
set dotenv-load # Auto-load .env file
set positional-arguments # Pass recipe args as $1, $2, etc.
set lazy # Defer evaluation of unused variables (v1.47.0; stable v1.48.0+)
set no-cd # Don't change to justfile directory for any recipe (v1.51.0+)
set default-list := true # Bare `just` lists recipes instead of running default (v1.52.0+)
set default-script := true # Make unannotated recipes script recipes; use sparingly (v1.52.0+)
set lists # Enable list-of-strings values; unstable, requires `set unstable` (v1.53.0+)Common Attributes
| Attribute | Purpose |
|---|---|
[arg("p", long, ...)] | Configure parameter as --flag option (v1.46) |
[arg("p", long, flag)] | Valueless flag ⇒ "true"/[]; needs set lists (v1.53+) |
[arg("p", pattern="…")] | Constrain parameter to match regex pattern |
[confirm("prompt")] | Require user confirmation (expressions OK as of v1.49) |
[doc("text")] | Override recipe documentation |
[env("NAME", "VALUE")] | Set env var for this recipe only (v1.47+, expr v1.51) |
[group("name")] | Group recipes in just --list output |
[macos] | Restrict a recipe to macOS |
[no-cd] | Don't change to justfile directory |
[parallel] | Run direct dependencies concurrently |
[positional-arguments] | Enable positional args for this recipe only |
[private] | Hide from just --list (same as _ prefix) |
[script] | Execute recipe as single script block |
[script("interpreter")] | Use specific interpreter (bash, python, etc.) |
[shell] | Force linewise shell mode under set default-script (v1.52+) |
[working-directory: "…"] | Run from given path (expressions OK as of v1.51) |
Recipe Argument Flags (v1.46.0+)
The [arg()] attribute configures parameters as CLI-style options:
# Long option (--target)
[arg("target", long)]
build target:
cargo build --target {{ target }}
# Short option (-v)
[arg("verbose", short="v")]
run verbose="false":
echo "Verbose: {{ verbose }}"
# Combined long + short
[arg("output", long, short="o")]
compile output:
gcc main.c -o {{ output }}
# Flag without value (presence sets the given value); under `set lists`, `flag` is the v1.53+ alternative
[arg("release", long, value="true")]
build release="false":
cargo build {{ if release == "true" { "--release" } else { "" } }}
# Help string (shown in `just --usage`)
[arg("target", long, help="Build target architecture")]
build target:
cargo build --target {{ target }}Usage examples:
just build --target x86_64
just build --target=x86_64
just compile -o main
just build --release
just --usage build # Show recipe argument helpMultiple attributes can be combined:
[no-cd, private]
[group("checks")]
recipe:
echo "hello"Built-in Constants
Terminal formatting constants are globally available (no definition needed):
| Constant | Description |
|---|---|
CYAN, GREEN, RED, YELLOW, BLUE, MAGENTA | Text colors |
BOLD, ITALIC, UNDERLINE, STRIKETHROUGH | Text styles |
NORMAL | Reset formatting |
BG_* | Background colors (BG_RED, BG_GREEN, etc.) |
HEX, HEXLOWER, HEXUPPER | Hexadecimal digits |
Usage:
@status:
echo -e '{{ GREEN }}Success!{{ NORMAL }}'
echo -e '{{ BOLD + CYAN }}Building...{{ NORMAL }}'Key Functions
# Require executable exists (fails recipe if not found)
jq := require("jq")
# Get environment variable with default
log_level := env("LOG_LEVEL", "info")
# Get justfile directory path
root := justfile_dir()
# Module location (useful inside `mod` files)
mod_path := module_path() # Full submodule path, e.g. "foo::bar"
mod_file := module_file() # Absolute path to module's justfile
mod_dir := module_directory() # Directory containing the module justfile
# Runtime directory (v1.49.0; typically $XDG_RUNTIME_DIR, falls back to tempdir)
rt := runtime_directory()
# Name of the currently-running recipe (v1.53.0+)
self:
echo "running {{ recipe_name() }}"User-Defined Functions (v1.49.0+)
Define reusable named expressions with name(args) := expression. Requires set unstable. Functions can reference module-level assignments.
set unstable
base := "foo"
join(extension) := base + "." + extension
# Use f-strings for interpolation
hello(name) := f"Hello, {{ name }}!"
create:
touch {{ join("c") }}
touch {{ join("html") }}
echo '{{ hello("World") }}'Use these to dedupe expression logic that would otherwise repeat across recipes; prefer them over backtick-evaluated variables when the value depends on input.
Lists (unstable, v1.53.0+)
set lists (also requires set unstable) turns values into lists of strings — still unstable and subject to breaking changes. Highlights:
set unstable
set lists
targets := ["x86", "arm"] # List literal (flattens; strings only)
all := targets ++ ["wasm"] # `++` concatenates lists
files := split("a.ts b.ts") # ["a.ts", "b.ts"] (whitespace by default)
root_ox_paths := [
"package.json",
".lintstagedrc.mjs",
".mcp.json",
"biome.jsonc",
"knip.jsonc",
"oxlint.config.ts",
"oxfmt.config.ts",
"tsconfig.base.json",
"vitest.shared.ts",
]
# Map a dependency over a list: invoked once per element, parallelized
[parallel]
build *platform: *(compile *platform)
compile platform:
echo "compiling for {{ platform }}"Use list literals for file/path collections. Do not recommend parenthesized, space-joined string assembly for path sets.
Booleans are reformed under set lists: canonical true is "true", canonical false is the empty list [] (every other value, including '', is truthy). !expr negates, ==/!=/=~/!~ work in any expression, and an if without else evaluates to [] when false. Variadic params (*args) become lists. New functions: split(), bool(), show(), join_list(). Full behavior in references/settings.md.
Recipe Patterns
When designing recipes that use status reporting, check/write semantics, or alias conventions, see references/patterns.md.
Inline Scripts
When writing recipes that need shell scripts (script attribute or shebang style), see references/inline-scripts.md. On stock macOS, bash resolves to /bin/bash 3.2 — see that file's Bash Version Pitfalls section before using Bash-4+ features.
Modules & Imports
Import Pattern
Include recipes from another file:
import "./just/settings.just"
import "./just/base.just"
import? "./local.just" # Optional (no error if missing)Module Pattern
Load submodule (requires set unstable):
mod foo # Loads foo.just or foo/justfile
mod bar "path/to/bar" # Custom path
mod? optional # Optional module
# Call module recipes
just foo::buildDevkit Import Pattern
For projects using @sablier/devkit:
import "./node_modules/@sablier/devkit/just/base.just"
import "./node_modules/@sablier/devkit/just/npm.just"Markdown Justfiles (v1.53.0+)
When --justfile points at a .md file, just extracts the contents of unindented ``` `just ``` fenced code blocks and runs them as a justfile. Useful for keeping runnable recipes inside documentation:
````markdown Build the project:
build:
echo Building…````
just --justfile README.md build--fmt prints the formatted justfile to stdout (rather than rewriting) when the source is a markdown file or stdin.
Section Organization
Standard section header format:
# ---------------------------------------------------------------------------- #
# DEPENDENCIES #
# ---------------------------------------------------------------------------- #Common sections (in order):
1. DEPENDENCIES - Required tools with URLs 2. CONSTANTS - Glob patterns, environment vars 3. RECIPES / COMMANDS - Main entry points 4. CHECKS - Code quality recipes 5. UTILITIES / INTERNAL HELPERS - Private helpers
Default Recipe
Define a curated default recipe when one action should be the entrypoint:
# Run all checks by default
default: full-checkIf no single default makes sense, prefer set default-list := true (v1.52.0+) over a default recipe that shells out to just --list:
set default-list := true
# Optional: still define recipes normally; bare `just` now lists them.
build:
cargo buildThe setting is per-module. It can also be forced at runtime with JUST_DEFAULT_LIST=true or just --default-list.
For compatibility with older just versions, keep the explicit listing recipe:
default:
@just --listDependencies Declaration
Document required tools at the top:
# ---------------------------------------------------------------------------- #
# DEPENDENCIES #
# ---------------------------------------------------------------------------- #
# Bun: https://bun.sh
bun := require("bun")
# Ni: https://github.com/antfu-collective/ni
na := require("na")
ni := require("ni")
nlx := require("nlx")
# Usage: invoke directly in recipes (not with interpolation)
build:
bun next buildNote: require() validates the tool exists at recipe evaluation time. Use the variable name directly (e.g., bun), not with interpolation ({{ bun }}).
Context7 Fallback
For Just features not covered in this skill (new attributes, advanced functions, edge cases), fetch the latest documentation:
Use context7 MCP with library ID `/websites/just_systems_man_en` to get up-to-date Just documentation.Example topics to search:
modules import mod- Module system detailssettings- All available settingsattributes- Recipe attributesfunctions- Built-in functionsscript recipes- Script block syntax
Additional Resources
Reference Files
For detailed patterns and comprehensive coverage, consult:
- [`references/settings.md`](references/settings.md) - Settings configuration and module system
- [`references/recipes.md`](references/recipes.md) - Recipe attributes, parameters, dependencies, and prefixes
- [`references/syntax.md`](references/syntax.md) - Constants, functions, variables, and CLI options
- [`references/patterns.md`](references/patterns.md) - Established conventions, section organization, helper patterns
Example Templates
Working justfile templates in examples/:
- [`devkit.just`](examples/devkit.just) - Minimal template importing @sablier/devkit
- [`standalone.just`](examples/standalone.just) - Full standalone template with all patterns
External Documentation
- Official Manual: https://just.systems/man/en/
- GitHub Repository: https://github.com/casey/just
- Context7 Library ID:
/websites/just_systems_man_en
No Justfile Formatter
Do not use just --fmt or just --dump. The user has bespoke formatting preferences that the built-in formatter does not respect. Preserve existing formatting as-is.
Tips
1. Use @ prefix to suppress command echo: @echo "quiet" 2. Use + for variadic parameters: test +args 3. Use * for optional variadic: build *flags 4. Quote glob patterns in variables: GLOBS := "\"**/*.json\"" 5. Use [no-cd] in monorepos to stay in current directory 6. Private recipes start with _ or use [private] 7. Always define aliases after recipe names for discoverability
policy:
allow_implicit_invocation: true
# See https://github.com/sablier-labs/devkit/blob/main/just/base.just
import "./node_modules/@sablier/devkit/just/base.just"
import "./node_modules/@sablier/devkit/just/npm.just"
# ---------------------------------------------------------------------------- #
# DEPENDENCIES #
# ---------------------------------------------------------------------------- #
# https://github.com/jqlang/jq
jq := require("jq")
# ---------------------------------------------------------------------------- #
# RECIPES #
# ---------------------------------------------------------------------------- #
# Default recipe
default:
@just --list
# Build the project
@build:
just clean
just tsc-build
alias b := build
# Clean the dist directory
@clean:
bunx del-cli dist
echo "✅ Cleaned build files"
# Run tests
test *args:
bun vitest run --hideSkippedTests {{args}}
alias t := test
# Run tests with UI
test-ui *args:
bun vitest --hideSkippedTests --ui {{args}}
alias tui := test-ui
set allow-duplicate-recipes
set allow-duplicate-variables
set shell := ["bash", "-euo", "pipefail", "-c"]
set unstable
# ---------------------------------------------------------------------------- #
# DEPENDENCIES #
# ---------------------------------------------------------------------------- #
# Bun: https://bun.sh
bun := require("bun")
# Ni: https://github.com/antfu-collective/ni
na := require("na")
ni := require("ni")
nlx := require("nlx")
# ---------------------------------------------------------------------------- #
# CONSTANTS #
# ---------------------------------------------------------------------------- #
GLOBS_PRETTIER := "\"**/*.{json,jsonc,yaml,yml}\""
# ---------------------------------------------------------------------------- #
# COMMANDS #
# ---------------------------------------------------------------------------- #
# Show available commands
default:
@just --list
# Install dependencies
[no-cd]
install *args:
ni {{ args }}
alias i := install
# Build the project
@build:
just clean
just tsc-build
alias b := build
# Compile TypeScript, emitting to the configured outDir
[no-cd]
@tsc-build compiler="tsc" project="tsconfig.json":
na {{ compiler }} --project {{ project }}
# Clean build artifacts
@clean:
bunx del-cli dist
echo "✅ Cleaned build files"
# ---------------------------------------------------------------------------- #
# CHECKS #
# ---------------------------------------------------------------------------- #
# Run all code checks
[group("checks")]
[no-cd]
@full-check:
just _run-with-status biome-check
just _run-with-status prettier-check
just _run-with-status tsc-check
echo ""
echo -e '{{ GREEN }}All code checks passed!{{ NORMAL }}'
alias fc := full-check
# Run all code fixes
[group("checks")]
[no-cd]
@full-write:
just _run-with-status biome-write
just _run-with-status prettier-write
echo ""
echo -e '{{ GREEN }}All code fixes applied!{{ NORMAL }}'
alias fw := full-write
# Check code with Biome
[group("checks")]
[no-cd]
@biome-check +globs=".":
na biome check {{ globs }}
alias bc := biome-check
# Lint code with Biome
[group("checks")]
[no-cd]
@biome-lint +globs=".":
na biome lint {{ globs }}
alias bl := biome-lint
# Fix code with Biome
[group("checks")]
[no-cd]
@biome-write +globs=".":
na biome check --write {{ globs }}
na biome lint --unsafe --write --only correctness/noUnusedImports {{ globs }}
alias bw := biome-write
# Check Prettier formatting
[group("checks")]
[no-cd]
@prettier-check +globs=GLOBS_PRETTIER:
na prettier --check --cache --no-error-on-unmatched-pattern {{ globs }}
alias pc := prettier-check
# Format using Prettier
[group("checks")]
[no-cd]
@prettier-write +globs=GLOBS_PRETTIER:
na prettier --write --cache --no-error-on-unmatched-pattern {{ globs }}
alias pw := prettier-write
# Type-check with TypeScript (no emit)
[group("checks")]
[no-cd]
@tsc-check compiler="tsc" project="tsconfig.json":
na {{ compiler }} --noEmit --project {{ project }}
alias tc := tsc-check
# ---------------------------------------------------------------------------- #
# TESTS #
# ---------------------------------------------------------------------------- #
# Run all tests
[group("test")]
test *args:
bun vitest run --hideSkippedTests {{ args }}
alias t := test
# Run tests with UI
[group("test")]
test-ui *args:
bun vitest --hideSkippedTests --ui {{ args }}
alias tui := test-ui
# ---------------------------------------------------------------------------- #
# UTILITIES #
# ---------------------------------------------------------------------------- #
# Private recipe to run a check with formatted output
[no-cd]
@_run-with-status recipe *args:
echo ""
echo -e '{{ CYAN }}→ Running {{ recipe }}...{{ NORMAL }}'
just {{ recipe }} {{ args }}
echo -e '{{ GREEN }}✓ {{ recipe }} completed{{ NORMAL }}'
alias rws := _run-with-status
Inline Scripts
When to read: when writing recipes that need shell scripts (script attribute or shebang style).
Just supports inline scripts in any language via two methods:
Script Attribute (Recommended)
Use [script("interpreter")] for cross-platform compatibility:
[script("node")]
fetch-data:
const response = await fetch('https://api.example.com/data');
const data = await response.json();
console.log(data);
[script("python3")]
analyze:
import json
with open('package.json') as f:
pkg = json.load(f)
print(f"Package: {pkg['name']}@{pkg['version']}")
[script("bash")]
deploy:
set -e
npm run build
aws s3 sync dist/ s3://bucket/Default Script Mode
Use set default-script := true (v1.52.0+) when most recipes should run as complete scripts instead of independent shell lines:
set default-script := true
set script-interpreter := ["bash", "-euo", "pipefail"]
deploy:
trap 'echo failed' ERR
npm run build
npm publish
[shell]
status:
echo "force this one back to linewise shell execution"[shell] overrides default-script for one recipe. set shell still controls linewise recipes and backticks; set script-interpreter controls [script] recipes with no explicit command.
Shebang Method
Use #!/usr/bin/env interpreter at the recipe start:
node-script:
#!/usr/bin/env node
console.log(`Node ${process.version}`);
console.log(JSON.stringify(process.env, null, 2));
python-script:
#!/usr/bin/env python3
import sys
print(f"Python {sys.version}")
bash-script:
#!/usr/bin/env bash
set -euo pipefail
echo "Running on $(uname -s)"When to use which:
[script()]- Cleaner multi-line recipe syntax- Shebang - Traditional Unix approach, works without
set unstable
Bash Version Pitfalls (macOS)
[script("bash")], #!/usr/bin/env bash, and set shell := ["bash", ...] all resolve bash via PATH. On stock macOS — and in minimal-PATH agent sandboxes — that is /bin/bash 3.2 (2007), not Homebrew's 5.x. Recipes written against Bash 4+ fail with signatures like:
mapfile: command not found
declare: -A: invalid optionBash-4+ features and 3.2-safe replacements:
| Bash 4+ feature | 3.2-safe replacement |
|---|---|
mapfile -t arr < file | while IFS= read -r line; do ...; done < file |
declare -A map | case statement or parallel arrays |
${var,,} / ${var^^} | tr '[:upper:]' '[:lower:]' (and inverse) |
${arr[-1]} | ${arr[${#arr[@]}-1]} |
| `cmd \ | & other` |
Pinning a newer interpreter works on this Apple Silicon/Homebrew profile when the path exists, but keep it explicit and guarded because agent sandboxes may expose only /bin/bash:
set shell := ["/opt/homebrew/bin/bash", "-euo", "pipefail", "-c"]3.2-safe version guard when a recipe genuinely needs Bash 4+ (BASH_VERSINFO exists in 3.2, so the check itself never breaks):
[script("bash")]
modern:
if [ "${BASH_VERSINFO[0]}" -lt 4 ]; then
echo "error: bash >= 4 required (found $BASH_VERSION)" >&2
exit 1
fi
declare -A map=([a]=1)Default recommendation: write recipe bodies that are Bash-3.2-safe. Pin /opt/homebrew/bin/bash only when a recipe genuinely needs Bash 4+ semantics.
Justfile Patterns & Conventions
Established patterns and conventions for organizing justfiles, based on real-world usage across multiple projects.
Section Headers
Use centered ASCII art headers to organize justfiles:
# ---------------------------------------------------------------------------- #
# SECTION #
# ---------------------------------------------------------------------------- #Constants Section
Glob Patterns
Quote glob patterns for shell expansion:
# ---------------------------------------------------------------------------- #
# CONSTANTS #
# ---------------------------------------------------------------------------- #
GLOBS_PRETTIER := "\"**/*.{json,jsonc,yaml,yml}\""
GLOBS_SOLIDITY := "{scripts,src,tests}/**/*.sol"
GLOBS_CLEAN := "**/{.logs,bindings,build,generated}"
GLOBS_CLEAN_IGNORE := "!graph/common/bindings"Path Lists
When a command consumes multiple concrete paths, require set unstable and set lists, then use list literals:
set unstable
set lists
root_ox_paths := [
"package.json",
".lintstagedrc.mjs",
".mcp.json",
"biome.jsonc",
"knip.jsonc",
"oxlint.config.ts",
"oxfmt.config.ts",
"tsconfig.base.json",
"vitest.shared.ts",
]
oxlint-check:
oxlint {{ root_ox_paths }}Do not recommend parenthesized, space-joined string assembly for path sets.
Environment Variables
export LOG_LEVEL := env("LOG_LEVEL", "info")
export NODE_ENV := env("NODE_ENV", "development")Recipe Groups
Use [group()] attribute for organized just --list output:
[group("checks")] # Linting, formatting, type checking
[group("codegen")] # Code generation
[group("test")] # Testing
[group("cli")] # CLI command helpers
[group("dev")] # Development utilities
[group("deploy")] # Deployment recipes
[group("print")] # Debug/print utilitiesMultiple groups per recipe:
[group("codegen"), group("envio")]
codegen-envio:
./codegen.shAlias Conventions
Define aliases immediately after recipe names for discoverability:
# Run all code checks
[group("checks")]
@full-check:
just _run-with-status biome-check
alias fc := full-checkExample Aliases
| Recipe | Alias | Purpose |
|---|---|---|
full-check | fc | Run all checks |
full-write | fw | Apply all fixes |
biome-check | bc | Biome check |
biome-write | bw | Biome fix |
Helper Patterns
Run-With-Status Pattern
Display formatted status during multi-step workflows:
# Private recipe to run a check with formatted output
@_run-with-status recipe *args:
echo ""
echo -e '{{ CYAN }}→ Running {{ recipe }}...{{ NORMAL }}'
just {{ recipe }} {{ args }}
echo -e '{{ GREEN }}✓ {{ recipe }} completed{{ NORMAL }}'
alias rws := _run-with-statusUsage in aggregate recipes:
[group("checks")]
@full-check:
just _run-with-status biome-check
just _run-with-status prettier-check
just _run-with-status tsc-check
echo ""
echo -e '{{ GREEN }}All code checks passed!{{ NORMAL }}'
alias fc := full-checkDefault Recipe
Prefer a meaningful default action when a project has one:
# Run all checks by default
default: full-checkWhen no single action is the obvious entrypoint, use default-list (v1.52.0+) instead of a wrapper recipe:
set default-list := trueKeep the explicit listing recipe only when supporting older just versions:
default:
@just --listMonorepo Patterns
Module Per Package
mod client "apps/client"
mod server "apps/server"
mod shared "packages/shared"No-CD for Cross-Package Commands
[no-cd]
build-all:
just client::build
just server::buildShared Recipes via Import
# apps/client/justfile
import "../../just/shared.just"
build: shared-setup
npm run buildScript Block Patterns
Multi-line Bash
[script("bash")]
deploy chain_slug:
set -e
case {{ chain_slug }} in
mainnet)
DEPLOY_URL="https://prod.example.com"
;;
testnet)
DEPLOY_URL="https://test.example.com"
;;
*)
echo "Unknown chain: {{ chain_slug }}"
exit 1
;;
esac
curl -X POST "$DEPLOY_URL/deploy"Conditional TUI Mode
[script("bash")]
envio command tui_mode="tui_on" *args:
set -a
if [ "{{ tui_mode }}" = "tui_off" ]; then
TUI_OFF=true
fi
pnpm envio {{ command }} {{ args }}Import Organization
Settings First
import "./just/settings.just"
import "./just/base.just"
import "./just/npm.just"Devkit Pattern
# See https://github.com/sablier-labs/devkit/blob/main/just/base.just
import "./node_modules/@sablier/devkit/just/base.just"
import "./node_modules/@sablier/devkit/just/npm.just"Local Overrides Last
import "./just/base.just"
import? "./just/local.just" # Optional local overridesAttribute Combinations
Private Group Recipe
[group("internal")]
[private]
_helper:
echo "helper"Confirmed No-CD Script
[no-cd]
[script("bash")]
[confirm("Deploy to production?")]
deploy-prod:
set -e
npm run build
aws s3 sync dist/ s3://prod-bucket/Documented Group Recipe
[doc("Generate TypeScript types from GraphQL schema")]
[group("codegen")]
codegen-types:
graphql-codegenEnvironment Variable Patterns
Export with Default
export LOG_LEVEL := env("LOG_LEVEL", "info")
export NODE_ENV := env("NODE_ENV", "development")Load from .env
set dotenv-load
# Or specify path
set dotenv-path := ".env.local"Per-Recipe Environment
test-integration:
DATABASE_URL="postgres://localhost/test" npm run test:integrationError Handling
Ignore Specific Errors
cleanup:
-rm -rf dist/
-rm -rf coverage/
echo "Cleanup complete"Assert Preconditions
deploy:
{{ assert(path_exists("dist/"), "Run 'just build' first") }}
aws s3 sync dist/ s3://bucket/Graceful Fallbacks
@version:
git describe --tags 2>/dev/null || echo "v0.0.0-dev"Just Recipes Reference
Recipe definition and behavior for Just command runner.
Attributes
Attributes modify recipe behavior. Place before recipe definition.
Recipe Visibility
# Private via underscore prefix
_helper:
echo "private"
# Private via attribute
[private]
helper:
echo "also private"Grouping
[group("checks")]
lint:
npm run lint
[group("checks")]
format:
npm run formatGroups organize just --list output:
Available recipes:
default
[checks]
format
lintDirectory Control
# Don't change to justfile directory
[no-cd]
status:
git status
# Set specific working directory
[working-directory: "packages/core"]
build-core:
npm run build
# Expression-valued working directory (v1.51.0+)
src := justfile_dir() / "src"
[working-directory: src]
generate:
codegenFor a justfile-wide opt-out, use set no-cd (v1.51.0+) instead of annotating every recipe.
OS-Restricted Recipes
Restrict a recipe to macOS when the command intentionally uses macOS tools.
[macos]
open url:
open {{ url }}This catalog is macOS-first; omit non-Mac OS guards unless the project itself must publish a portable justfile.
Script Blocks
# Default shell script
[script]
multiline:
if [ -f "config.json" ]; then
echo "Found config"
else
echo "No config"
fi
# Specific interpreter
[script("python3")]
process:
import json
data = json.load(open("config.json"))
print(data["name"])
[script("bash")]
deploy:
set -e
npm run build
aws s3 sync dist/ s3://bucket/
[script("node")]
analyze:
const fs = require('fs');
const pkg = JSON.parse(fs.readFileSync('package.json'));
console.log(`Package: ${pkg.name}@${pkg.version}`);With set default-script := true (v1.52.0+), unannotated recipes are script recipes by default. Add [shell] to force normal linewise shell execution for a specific recipe:
set default-script := true
[shell]
quick:
echo "run this as a shell line, not a temp script"Confirmation
# Default confirmation prompt
[confirm]
delete-all:
rm -rf dist/
# Custom prompt
[confirm("Are you sure you want to deploy to production?")]
deploy-prod:
./deploy.sh production
# Expression-valued prompt (v1.49.0+): skip the prompt in CI
[confirm(if env("CI", "") == "true" { "" } else { "Deploy to prod?" })]
deploy:
./deploy.shPer-Recipe Environment Variables
The [env(NAME, VALUE)] attribute (v1.47.0+) sets environment variables scoped to one recipe — narrower than export (whole justfile) or set dotenv-load (everything).
[env("RUST_BACKTRACE", "1")]
test:
cargo test # RUST_BACKTRACE=1 is set only for `test`
# Multiple env attributes stack
[env("NODE_ENV", "test")]
[env("LOG_LEVEL", "debug")]
test-integration:
bun test:integrationAs of v1.51.0, the value accepts arbitrary expressions, including other variables and functions:
build_id := `git rev-parse --short HEAD`
[env("BUILD_ID", build_id)]
[env("CACHE_DIR", justfile_dir() / ".cache")]
build:
cargo build[env(...)] is the preferred mechanism for one-recipe env overrides — it composes with set dotenv-load and module-level exports (which it overrides as of v1.51.0).
Parallel Execution
Marks a recipe so its direct dependencies run concurrently instead of sequentially. Introduced in just 1.42.0 (casey/just#2803).
[parallel]
ci: lint test build
lint:
bun lint
test:
bun test
build:
bun run buildBehavior:
- Only direct dependencies fan out. The recipe body itself still runs after all parallel deps finish.
- Shared transitive deps run exactly once before the parallel fan-out, so this is the right primitive for
setup → fan-out work graphs — a dep referenced by multiple parallel siblings is deduped, not run once per sibling.
- No concurrency cap is exposed; just launches all direct deps at once.
- Empirical timing on a sleep-0.5 fan-out of three deps: 1.54 s sequential → 0.54 s with `[parallel]`.
Pairs naturally with parameterized dependencies (recipe: (sub-recipe "arg1" "arg2")) for table-driven fan-out where every sibling shares a setup step:
[parallel]
check: \
(_check "lint") \
(_check "test") \
(_check "types")
_check kind: setup
./scripts/check.sh {{ kind }}
setup:
bun install --frozen-lockfileHere setup runs once thanks to transitive dedup, then the three _check invocations run concurrently.
Documentation
# Comment becomes doc (default)
# Build the project
build:
npm run build
# Override with attribute
[doc("Compile TypeScript and bundle")]
build:
npm run build
# Suppress documentation
[doc]
internal-helper:
echo "hidden"Combining Attributes
# Same line (comma-separated)
[no-cd, private]
helper:
echo "helper"
# Multiple lines
[group("codegen")]
[script("bash")]
[confirm("Generate bindings?")]
codegen:
./generate.shPer-Recipe Positional Arguments
[positional-arguments]
@greet name:
echo "Hello, $1!"Recipe Parameters
Required Parameters
greet name:
echo "Hello, {{ name }}"Default Parameters
greet name="World":
echo "Hello, {{ name }}"Variadic Parameters
# One or more arguments (required)
test +files:
npm test {{ files }}
# Zero or more arguments (optional)
build *flags:
npm run build {{ flags }}Parameter with Environment Variable
# Set from env or use default
deploy env=env("DEPLOY_ENV", "staging"):
./deploy.sh {{ env }}Recipe Argument Flags (v1.46.0+)
The [arg()] attribute configures parameters as command-line options.
Long Options
Use long to accept --name style options:
# Explicit long name
[arg("target", long="target")]
build target:
cargo build --target {{ target }}
# Default to parameter name (recommended)
[arg("target", long)]
build target:
cargo build --target {{ target }}
# Usage:
# just build --target x86_64
# just build --target=x86_64Short Options
Use short to accept -x style options:
[arg("verbose", short="v")]
run verbose="false":
echo "Verbose: {{ verbose }}"
# Usage: just run -v trueCombined Long and Short
A parameter can accept both styles:
[arg("output", long="output", short="o")]
compile output:
gcc main.c -o {{ output }}
# Usage:
# just compile --output main
# just compile -o mainFlags Without Values
Use value (v1.46.0+) for boolean-style flags that set a predefined value when present. Give the parameter a default so the flag is optional. This is the stable form and works on any just ≥ 1.46.0:
[arg("release", long, value="true")]
build release="false":
cargo build {{ if release == "true" { "--release" } else { "" } }}
# Usage:
# just build → release="false" (default)
# just build --release → release="true"Under set lists (unstable, v1.53.0+), the flag keyword is an alternative: presence sets the parameter to "true", absence leaves it [] (the canonical false). Flag parameters may not have a default, and set unstable + set lists are required:
set unstable
set lists
[arg("release", long, flag)]
build release:
cargo build {{ if release == "true" { "--release" } else { "" } }}
# Usage:
# just build → release=[] (falsy)
# just build --release → release="true"Help Strings
Use help to add descriptions visible in just --usage:
[arg("target", long, help="Target architecture")]
[arg("release", long, value="true", help="Build in release mode")]
build target release="false":
cargo build --target {{ target }}$ just --usage build
Usage: just build [OPTIONS] --target <target>
Arguments:
--target <target> Target architecture
--release Build in release modeMultiple arg Attributes
Each parameter with options needs its own [arg()]:
[arg("input", long, short="i", help="Input file")]
[arg("output", long, short="o", help="Output file")]
[arg("verbose", long, short="v", value="true")]
convert input output verbose="false":
convert {{ input }} {{ output }} {{ if verbose == "true" { "-v" } else { "" } }}Pattern Constraints
Use pattern to constrain arguments to match a regular expression:
# Require numeric input
[arg('n', pattern='\d+')]
double n:
echo $(({{n}} * 2))
# Usage:
# just double 5 → valid
# just double abc → error: argument doesn't match patternUse the | operator to constrain to specific alternatives:
[arg('flag', pattern='--help|--version')]
info flag:
just {{flag}}
# Usage:
# just info --help → valid
# just info --version → valid
# just info --foo → error: argument doesn't match patternArg Attribute Syntax Summary
| Option | Description |
|---|---|
long | Accept --param (defaults to name) |
long="name" | Accept --name |
short="x" | Accept -x |
value="val" | Set this value when flag present (stable; v1.46.0+) |
flag | Valueless flag ⇒ "true"/[]; needs set lists, no default (v1.53.0+) |
help="text" | Description for just --usage |
pattern="regex" | Constrain argument to match regex |
Recipe Dependencies
Simple Dependencies
build: clean compile
echo "Build complete"
clean:
rm -rf dist/
compile:
tscDependencies with Arguments
deploy env: (build env)
./deploy.sh {{ env }}
build env:
npm run build:{{ env }}Conditional Execution
test: && lint
npm test
# lint runs only if test succeedsMapped Dependencies over Lists (unstable, v1.53.0+)
With set lists, a dependency can be invoked once per element of a list argument using *(recipe *arg). Combine with [parallel] to fan out concurrently:
set unstable
set lists
[parallel]
build target *platform: *(compile target *platform)
@compile target platform:
echo compiling {{ target }} for {{ platform }}…$ just build x86 foo bar
compiling foo for x86…
compiling bar for x86…Each argument to a non-mapped dependency binds to exactly one parameter; passing extra arguments to a variadic dependency is an error.
Command Prefixes
| Prefix | Effect |
|---|---|
@ | Don't echo command |
- | Ignore errors |
@- or -@ | Both |
@quiet:
echo "Only output shown"
-ignore-error:
false
echo "Still runs"
@-both:
false
echo "Quiet and ignores error"Shebang Recipes
Execute with specific interpreter:
python-script:
#!/usr/bin/env python3
import sys
print(f"Python {sys.version}")
node-script:
#!/usr/bin/env node
console.log(process.version)Just Settings & Modules Reference
Configuration and module system for Just command runner.
Settings
Settings configure global behavior and must appear at the top of the justfile.
Boolean Settings
Enable with set NAME or set NAME := true:
| Setting | Description |
|---|---|
allow-duplicate-recipes | Allow later recipes to override earlier ones |
allow-duplicate-variables | Allow later variables to override earlier ones |
default-list | List recipes instead of running the default recipe (v1.52.0+) |
default-script | Treat unannotated recipes as script recipes (v1.52.0+) |
dotenv-load | Load .env file automatically |
dotenv-required | Error if .env file is missing |
export | Export all variables as environment variables |
fallback | Search parent directories for justfile |
ignore-comments | Don't print comments in recipe listings |
lazy | Defer evaluation of unused variables (v1.47.0; stable v1.48.0+) |
lists | Enable list-of-strings values (unstable, requires set unstable; v1.53.0+) |
no-cd | Don't change to justfile directory for any recipe (v1.51.0+) |
positional-arguments | Pass recipe arguments as $1, $2, etc. |
quiet | Don't echo recipe lines |
unstable | Enable unstable features (user-defined functions, eager keyword, lists, etc.) |
Value Settings
| Setting | Example | Description |
|---|---|---|
shell | ["bash", "-euo", "pipefail", "-c"] | Shell and arguments for linewise recipes |
script-interpreter | ["bash", "-euo", "pipefail"] | Interpreter for empty [script] recipes |
dotenv-filename | ".env.local" | Custom dotenv filename; accepts a list to load multiple files (v1.53.0+) |
dotenv-path | "config/.env" | Custom dotenv path; accepts a list to load multiple files (v1.53.0+) |
tempdir | "/tmp/just" | Temporary file directory |
working-directory | "src" | Default working directory |
Const expressions in settings (v1.46.0+):
All settings now accept const expressions:
project_name := "myapp"
src_dir := "src"
set working-directory := src_dir
set dotenv-filename := project_name + ".env"Recommended Settings
set allow-duplicate-recipes
set allow-duplicate-variables
set shell := ["bash", "-euo", "pipefail", "-c"]
set unstableShell flags explained:
-e: Exit immediately on error-u: Treat unset variables as errors-o pipefail: Pipeline fails if any command fails-c: Execute following string as command
Note: bash here resolves via PATH. In agent sandboxes and stock macOS environments this can be /bin/bash 3.2, even when Homebrew Bash exists at /opt/homebrew/bin/bash; see inline-scripts.md > Bash Version Pitfalls before relying on Bash-4+ features in recipe bodies.
Lazy Evaluation (v1.47.0; stable v1.48.0+)
set lazy skips evaluating any variable that no executed recipe references. Useful when top-level assignments invoke shell commands or remote calls that are only needed by some recipes.
set lazy
# Only evaluated when a recipe actually uses `token`.
token := `gh auth token`
deploy:
curl -H "Authorization: Bearer {{ token }}" https://example.com/deploy
clean:
rm -rf dist/ # `token` never evaluated here.Assignments marked export (or living in a module with set export) are always evaluated, even under lazy.
To force evaluation of a normally-unused assignment under lazy, prefix it with eager (requires set unstable):
set unstable
set lazy
eager schema_version := `cat schema/VERSION` # Evaluated even if no recipe uses it.Default Listing (v1.52.0+)
Use default-list when no recipe should be the default entrypoint:
set default-list := true
build:
cargo build
test:
cargo testWith this setting, bare just lists recipes instead of running the [default] recipe or first recipe. The setting is per-module, so invoking a module path with default-list enabled lists that module's recipes.
Runtime alternatives:
JUST_DEFAULT_LIST=true just
just --default-list
just --list foo::barDefault Script Recipes (v1.52.0+)
Use default-script only in script-heavy justfiles where most recipes should run as whole-file scripts rather than as linewise shell commands:
set default-script := true
set script-interpreter := ["bash", "-euo", "pipefail"]
deploy:
trap 'echo failed' ERR
npm run build
npm publish
[shell]
status:
echo "linewise shell recipe"set shell still controls linewise shell recipes and backticks. set script-interpreter controls [script] recipes with no explicit command, including unannotated recipes when default-script is enabled.
Lists (unstable, v1.53.0+)
set lists introduces a list-of-strings value type. It is unstable and will change in backwards-incompatible ways — gate it behind set unstable and track the `set lists` issue. Enabling it changes several behaviors:
set unstable
set lists
targets := ["x86", "arm"] # List literal; literals flatten and hold only strings:
# [["a", "b"], [], "c"] == ["a", "b", "c"]
all := targets ++ ["wasm"] # `++` concatenates lists
prefixed := "build-" + targets # `+`/`/` broadcast a string across each element
pairs := ["a", "b"] / ["1", "2"] # equal-length lists combine pairwise → ["a/1", "b/2"]
parts := split("a.ts b.ts") # ["a.ts", "b.ts"]; separator defaults to whitespace (trimmed)For file/path collections, use list literals:
root_ox_paths := [
"package.json",
".lintstagedrc.mjs",
".mcp.json",
"biome.jsonc",
"knip.jsonc",
"oxlint.config.ts",
"oxfmt.config.ts",
"tsconfig.base.json",
"vitest.shared.ts",
]Do not build path sets with parenthesized, space-joined string assembly.
Recipes & dependencies:
- Variadic parameters (
*args,+args) are lists of strings instead of one space-joined string. - A parameter evaluates to its default when given an empty list; passing
[]to a non-*parameter without a default is an error. - Map a dependency over a list with
*(recipe *arg)— see recipes.md. - Lists in recipe and
f-string interpolations are space-joined into a single string.
Booleans (reformed under `set lists`): canonical true is "true"; canonical false is the empty list []. Every other value is truthy, including `''`.
!exprevaluates to"true"whenexpris[], else[].==,!=,=~,!~work in any expression (not justif/assert) and evaluate to"true"or[].==/!=check structural equality.value =~ regexesis true if any element ofvaluematches any regex inregexes(false if either is empty);!~is the negation.- An
ifwith noelseevaluates to[]when its condition is false.
Functions that accept lists: quote(), append(), prepend(), absolute_path() map over each element; env() and [env] take a list of keys / set the joined value ([] unsets the variable); which() now requires set lists and returns [] when not found; is_dependency(), path_exists(), semver_matches() return the canonical booleans.
New functions:
| Function | Behavior |
|---|---|
split(s, sep) | Split s into a list on sep; default splits on whitespace with ends trimmed |
join_list(v, sep) | Join list v into one string (default separator is a single space) — bridge to un-upgraded funcs |
bool(v) | [] for ""/"0"/"false"/[], "true" for "1"/"true"; any other value errors |
show(v) | Literal representation, e.g. "[]", ["foo", "bar"]; single-element lists render as the element |
Caveat: using a list where a string is expected is an error — reach for join_list() (or interpolation) to bridge to functions not yet list-aware.
Multiple `.env` files: under set lists, dotenv-path and dotenv-filename accept lists (and the matching --dotenv-path/--dotenv-filename flags may be repeated). dotenv-path values are tried first; otherwise the dotenv-filename names are searched in the current directory and its ancestors. When several files load, later entries override earlier ones.
Modules & Imports
Imports
Include another justfile's contents directly:
# Required import (error if missing)
import "path/to/file.just"
import "./just/settings.just"
# Optional import (no error if missing)
import? "local-overrides.just"Import behavior:
- Imported recipes and variables merge into current namespace
- Later definitions override earlier ones (with
allow-duplicate-*) - Relative paths resolve from importing file's directory
- Duplicate imports are deduplicated automatically
Modules
Load justfile as a submodule (requires set unstable):
# Load from foo.just or foo/justfile
mod foo
# Load from custom path
mod bar "path/to/bar.just"
mod baz "other/directory" # Looks for justfile inside
# Optional module (no error if missing)
mod? local
# Module with attributes
[private]
mod internal
[doc("Development tools")]
mod devAs of v1.52.0, aliases and recipes that depend on an absent optional module are disabled with clearer errors instead of failing unrelated listings or invocations.
Calling module recipes:
# Subcommand syntax
just foo build
# Path syntax
just foo::build
# From another recipe
@all:
just foo::build
just bar::testModule namespacing:
- Recipes inside modules are namespaced:
module::recipe - Variables inside modules are NOT accessible from parent
- Settings inside modules apply only to that module
- Modules can import/include other files
Overriding module variables (v1.48.0+):
Override := assignments inside submodules from the command line with a ::-separated path:
just foo::log_level=debug foo::run
just --set foo::log_level debug foo::runEither form works; both target the submodule's own variable, not the parent's.
Module Search Paths
When using mod foo:
1. foo.just in same directory 2. foo/justfile subdirectory 3. foo/mod.just subdirectory
Just Syntax Reference
Language syntax and utilities for Just command runner.
Constants
Terminal Colors
Available globally without definition:
| Constant | ANSI Code |
|---|---|
BLACK | \e[30m |
RED | \e[31m |
GREEN | \e[32m |
YELLOW | \e[33m |
BLUE | \e[34m |
MAGENTA | \e[35m |
CYAN | \e[36m |
WHITE | \e[37m |
Text Styles
| Constant | Effect |
|---|---|
BOLD | Bold text |
ITALIC | Italic text |
UNDERLINE | Underlined text |
STRIKETHROUGH | Strikethrough text |
INVERT | Invert colors |
HIDE | Hidden text |
Reset
| Constant | Effect |
|---|---|
NORMAL | Reset all formatting |
Background Colors
| Constant | Description |
|---|---|
BG_BLACK | Black background |
BG_RED | Red background |
BG_GREEN | Green background |
BG_YELLOW | Yellow background |
BG_BLUE | Blue background |
BG_MAGENTA | Magenta background |
BG_CYAN | Cyan background |
BG_WHITE | White background |
System Constants
| Constant | Value |
|---|---|
HEX | 0123456789abcdef |
HEXLOWER | 0123456789abcdef |
HEXUPPER | 0123456789ABCDEF |
PATH_SEP | / on macOS |
PATH_VAR_SEP | : on macOS |
Usage Examples
@success:
echo -e '{{ GREEN }}✓ Success!{{ NORMAL }}'
@error:
echo -e '{{ RED + BOLD }}✗ Error!{{ NORMAL }}'
@highlight:
echo -e '{{ BG_YELLOW + BLACK }}Warning{{ NORMAL }}'
@combined:
echo -e '{{ BOLD + UNDERLINE + CYAN }}Important{{ NORMAL }}'Functions
Executable Functions
# Require executable (fail if not found)
jq := require("jq")
# Returns full path: /usr/bin/jq
# Usage: invoke directly in recipes (not with interpolation)
process:
jq '.name' package.json
# Check if executable exists
has_docker := `which docker > /dev/null 2>&1 && echo "true" || echo "false"`Note: require() validates the tool exists and stores its path. Use the variable name directly (e.g., jq), not with interpolation ({{ jq }}).
Environment Functions
# Get env var (error if unset)
home := env("HOME")
# Get env var with default
log_level := env("LOG_LEVEL", "info")
# Export variable
export DATABASE_URL := env("DATABASE_URL", "postgres://localhost/dev")Path Functions
# Justfile directory (absolute path)
root := justfile_dir()
# Justfile path
justfile := justfile()
# Source directory (for imported files)
source_dir := source_directory()
source_file := source_file()
# Invocation directory (where just was called from)
invocation_dir := invocation_directory()
# Module location (meaningful inside `mod` files)
mod_file := module_file() # Absolute path to the module's justfile (v1.49.0+)
mod_dir := module_directory() # Directory containing the module justfile (v1.49.0+)
mod_path := module_path() # Submodule path, e.g. "foo::bar" (v1.50.0+)
# Runtime directory (v1.49.0; $XDG_RUNTIME_DIR or platform fallback)
rt := runtime_directory()
# Parent directory
parent := parent_directory(justfile_dir())
# Join paths
config := join(justfile_dir(), "config")
# File operations
exists := path_exists("config.json")
stem := file_stem("config.json") # "config"
name := file_name("path/config.json") # "config.json"
ext := extension("config.json") # "json"String Functions
# Case conversion
upper := uppercase("hello") # "HELLO"
lower := lowercase("HELLO") # "hello"
kebab := kebabcase("HelloWorld") # "hello-world"
snake := snakecase("HelloWorld") # "hello_world"
title := titlecase("hello") # "Hello"
# String manipulation
trimmed := trim(" hello ") # "hello"
replaced := replace("foo-bar", "-", "_") # "foo_bar"
# Quoting
quoted := quote("path with spaces") # "'path with spaces'"
shell_escaped := shell("echo 'test'")System Functions
# Operating system
os := os() # "macos" on this catalog's target machine
family := os_family() # "unix" on macOS
arch := arch() # "x86_64", "aarch64", etc.
# Invocation info (only meaningful inside a recipe)
dep := is_dependency() # "true" when run as another recipe's dependency
this:
echo {{ recipe_name() }} # name of the running recipe (v1.53.0+)
# Number of CPUs
cpus := num_cpus()
# UUID generation
id := uuid()
# SHA256 hash
hash := sha256("content")
file_hash := sha256_file("config.json")
# Date/time
now := datetime("%Y-%m-%d")
timestamp := datetime("%s")Conditional Functions
# Error if condition false
_ := assert(path_exists("config.json"), "Config file required!")
# Message is optional as of v1.53.0; assert() evaluates to its condition
_ := assert(path_exists("config.json"))
# Conditional value
mode := if env("CI", "") != "" { "ci" } else { "local" }
# Error message
_ := error("This recipe is deprecated")User-Defined Functions (v1.49.0+)
Define reusable named expressions with name(args) := expression. Requires set unstable. Functions live in the same namespace as variables and may reference any module-level assignment.
set unstable
base := "src"
join(extension) := base + "/main." + extension
# f-strings for interpolation
greet(name) := f"Hello, {{ name }}!"
# Compose with other functions and conditionals
asset(name) := if path_exists(join("ts")) { name + ".ts" } else { name + ".js" }
build:
cp {{ join("ts") }} dist/
echo '{{ greet("world") }}'When to use:
- Replace repeated expression fragments across recipes.
- Prefer over backtick-evaluated variables when the value depends on a parameter (backticks evaluate once at
justfile parse time; functions evaluate per call site).
- Function bodies are pure expressions — no shell, no side effects. For shell logic, use a
[script]recipe.
Lists & Booleans (unstable, v1.53.0+)
set lists (requires set unstable) adds a list-of-strings type and reforms booleans. Operators and functions it adds or changes:
| Construct | Meaning |
|---|---|
[a, b, c] | List literal (flattens nested lists; strings only) |
++ | List concatenation |
+, / | Broadcast a string across a list, or combine equal-length lists pairwise |
!expr | Negation: "true" when expr is [] (false), else [] |
== != =~ !~ | Usable in any expression (not just if/assert); yield "true" or [] |
if cond { x } | else may be omitted; evaluates to [] when cond is false |
split(s, sep) | Split string into a list (default: on whitespace, trimmed) |
join_list(v, sep) | Join a list into one string (default separator: single space) |
bool(v) | Parse canonical booleans; errors on non-boolean values |
show(v) | Literal representation of a value (useful in --evaluate / debugging) |
Canonical true is "true"; canonical false is the empty list [] (all else is truthy). Full semantics, list-aware built-ins, and multiple-.env behavior: settings.md > Lists.
Variables
Assignment
# Simple
name := "value"
# From environment
port := env("PORT", "3000")
# From shell command
version := `git describe --tags`
# Exported (available to recipes)
export NODE_ENV := "production"
# Conditional
mode := if os() == "macos" { "local-mac" } else { "other" }Variable Scope
- Variables defined at top level are global
- Recipe parameters shadow global variables
- Imported variables can be overridden with
allow-duplicate-variables
Backtick Evaluation
````just
Single line
version := git describe --tags
Multi-line (indented)
files := ``` find src -name "*.ts" \ | grep -v test \ | head -10
Just CLI Options
| Option | Description |
|---|---|
just --list | List available recipes |
just --list MODULE::PATH | List recipes in a submodule |
just --list --unsorted | List in source order |
just --list --group NAME | Filter --list to one group (v1.47.0+) |
just --default-list | Make bare just list recipes instead of running the default (v1.52.0+) |
just --summary | Brief recipe list |
just --show RECIPE | Show recipe source |
just --usage RECIPE | Show recipe argument usage (v1.46.0+) |
just --dry-run RECIPE | Print commands without running |
just --time RECIPE | Print recipe execution time (v1.49.0+) |
just --evaluate | Print all variables |
just --evaluate-format FMT | Format --evaluate output (json, shell; v1.49.0+) |
just --evaluate VAR | Evaluate a single variable/module path (v1.49.0+) |
just --json | Dump justfile metadata as JSON (v1.48.0+; alias of --dump --dump-format json) |
just --fmt | Format justfile (do not use — see SKILL.md) |
just --fmt --check | Check formatting |
just --choose | Interactive recipe selection (fzf) |
just --choose --group NAME | Restrict --choose to one group (v1.50.0+) |
just -f PATH | Use specific justfile (-f - reads stdin as of v1.51.0) |
just -f FILE.md RECIPE | Extract & run ``` `just ``` blocks from a markdown file (v1.53.0+) |
just --justfile-name NAME | Override justfile filename for auto-discovery (v1.49.0+) |
just --dotenv-path P … | Load dotenv file(s); repeatable to load several (v1.53.0+) |
just --dotenv-filename N … | Dotenv filename(s) to search for; repeatable (v1.53.0+) |
just -d DIR | Set working directory |
just --indentation STR | Use STR for recipe indentation when formatting (v1.49.0+) |
Glob Patterns
Store glob patterns in variables with proper quoting:
# Quote the pattern
GLOBS_TS := "\"**/*.{ts,tsx}\""
GLOBS_JSON := "\"**/*.{json,jsonc,yaml,yml}\""
# Use in recipes
lint:
eslint {{ GLOBS_TS }}
format:
prettier --check {{ GLOBS_JSON }}Error Handling
# Fail on any error (in script block)
[script("bash")]
deploy:
set -e
npm run build
npm run test
npm publish
# Continue on error (line prefix)
cleanup:
-rm -rf dist/
-rm -rf node_modules/
echo "Cleanup attempted"
# Assert condition
check:
{{ assert(path_exists("package.json"), "Must run from project root") }}1.53.0
Related skills
How it compares
Choose cli-just when standardizing on Just and you want agents to author version-aware justfiles; use Makefile or package.json script skills when your repo does not adopt Just.
FAQ
What Just version does cli-just target?
cli-just targets Just v1.55.0, the latest stable release noted in the skill. Features introduced in earlier versions (such as v1.46 [arg()] flags) are tagged so you know the minimum Just version required.
What files does cli-just include?
cli-just ships a main SKILL.md plus 5 reference markdown files (settings, recipes, syntax, patterns, inline-scripts) and 2 example justfile templates (devkit.just and standalone.just) for standalone or devkit-based projects.
Does cli-just require unstable Just features?
Some cli-just patterns need `set unstable`, including user-defined functions (v1.49+), list values under `set lists` (v1.53+), and `[cache]` on script recipes (v1.54+). The skill marks which features require unstable mode.