Argc CLI Framework
Purpose
This skill enables working with the argc framework - a bash-based system that transforms comment annotations into full-featured command-line interfaces with automatic argument parsing, validation, help generation, and shell completions.
Argc has two components:
-
argc framework - The parser/engine that reads comment tags and generates CLI functionality
-
argc-completions - 1000+ pre-built completion scripts for common commands, using argc as the engine
When to Use
Trigger this skill when:
-
Creating or modifying Argcfile.sh task runner files
-
Converting existing bash scripts to use argc argument parsing
-
Adding command-line argument handling to bash scripts
-
Implementing shell completions for custom commands
-
Building CLI tools that need subcommands, flags, options, or positional arguments
-
Setting up task automation similar to Make but using bash
How to Use This Skill
Core Workflow
CRITICAL: Always use argc --argc-eval to understand what's happening
You're good at bash, not argc. The fastest way to learn argc is to see the bash code it generates:
Don't just run: argc +.md process file.md
Instead, SEE what it generates:
argc --argc-eval Argcfile.sh +.md process file.md
This shows you exactly what bash code argc creates. Once you see the generated code, you can understand it because it's just bash.
Why this matters:
-
Argc is code generation, not magic
-
The eval output IS the implementation
-
Validation errors are generated bash (heredoc + exit 1)
-
Success cases are variable assignments + function calls
-
Hooks (_argc_before , _argc_after ) are visible in the execution order
Integration pattern - Every argc script requires the eval line:
eval "$(argc --argc-eval "$0" "$@")"
The argc --argc-eval command reads comment tags, validates arguments, and generates bash code. The eval executes this generated code.
Success case - Valid arguments generate variable setup and function invocation:
$ argc --argc-eval Argcfile.sh +.md process ./argc-skill/SKILL.md export ARGC_PWD=/home/skogix/dev/skogargc export ARGC_VARS=YXJnY19maWxldHlwZT0ubWQ7YXJnY19vdXRwdXQ9Li9vdXQ7... argc_filetype=.md # Symbol value argc_output=./out # Option with default argc_files=( ./argc-skill/SKILL.md ) # Validated argument argc__args=( Argcfile +.md process ./argc-skill/SKILL.md ) argc__fn=process argc__positionals=( ./argc-skill/SKILL.md ) _argc_before # Hook runs first process ./argc-skill/SKILL.md # Command executes _argc_after # Hook runs last
Validation error - Wrong filetype shows argc validated the symbol first, then failed on file:
$ argc --argc-eval Argcfile.sh +.sh process ./argc-skill/SKILL.md
export ARGC_PWD=/home/skogix/dev/skogargc
command cat >&2 <<-'EOF'
error: invalid value ./argc-skill/SKILL.md for <FILES>...
[possible values: ./argc-skill/examples/hooks.sh, ./argc-skill/examples/demo.sh, ...]
EOF
exit 1
The symbol +.sh set argc_filetype=.sh , which made _choice_files only return .sh files. The .md file failed validation because it wasn't in the filtered list.
Missing required argument - Generates error and exit:
argc --argc-eval examples/quick-start.sh
command cat >&2 <<-'EOF' error: the following required arguments were not provided: <FILE> EOF exit 1
Key points:
-
Validation happens during argc --argc-eval , before your bash code runs
-
Error path: heredoc to stderr + exit 1 (script never runs)
-
Success path: variable setup + function execution
-
Flag values are 0 or 1 , not booleans
-
Multi-value arguments become bash arrays: ${argc_files[@]}
-
Built-in variables (argc__args , argc__fn , argc__positionals ) available for advanced use
Use comment tags - Define CLI structure through bash comments prefixed with @ :
@describe Command description
@flag -v --verbose Enable verbose output
@option -f --file Input file path
@arg name! Required positional argument
Access parsed values - Argc generates variables with argc_ prefix:
echo $argc_verbose # Flag value (0 or 1) echo $argc_file # Option value echo $argc_name # Argument value
Multi-value parameters become arrays:
@arg files*
@option --include*
for file in "${argc_files[@]}"; do echo "File: $file" done
echo "Includes: ${argc_include[@]}" echo "Count: ${#argc_files[@]}"
Built-in variables for advanced use:
-
argc__args
-
Array of all CLI arguments
-
argc__positionals
-
Array of positional arguments only (no flags/options)
-
argc__fn
-
Name of the function being executed
Environment variables injected by argc:
-
ARGC_PWD
-
Original working directory (available in Argcfile.sh)
-
ARGC_SHELL_PATH
-
Path to shell executable (configurable)
-
ARGC_SCRIPT_NAME
-
Override default Argcfile.sh name (configurable)
Available Comment Tags
Reference these tags when building argc scripts:
Tag Purpose Example
@describe
Command description
@describe My CLI tool
@cmd
Define subcommand
@cmd build Build it
@alias
Subcommand aliases
@alias b
@arg
Positional argument
@arg file!
@option
Option argument
@option --output
@flag
Boolean flag
@flag -v --verbose
@env
Environment variable
@env API_KEY!
@meta
Metadata
@meta version 1.0
Use "!" suffix for required parameters and "*" suffix for multi-value arrays.
@cmd vs @describe - When to Use Which
Use @describe for single-script invocations:
#!/bin/bash
@describe Process multiple files with validation
@arg inputs* Input files to process
@option --output=./out Output directory
@flag -v --verbose
main() { [[ "$argc_verbose" == "1" ]] && echo "Processing ${#argc_inputs[@]} files" for file in "${argc_inputs[@]}"; do echo "Processing: $file -> $argc_output/$(basename "$file")" done }
eval "$(argc --argc-eval "$0" "$@")"
Run directly: ./script.sh file1.txt file2.txt file3.txt --verbose
Use @cmd for Argcfile.sh task runners or scripts with subcommands:
#!/bin/bash
Argcfile.sh
@cmd build Build the project
build() { echo "Building..." }
@cmd test Run tests
test() { echo "Testing..." }
eval "$(argc --argc-eval "$0" "$@")"
Run with subcommand: argc build or argc test
Rule: @describe describes the main entry point. @cmd defines subcommands (tasks). Use @cmd when you have multiple functions that users can invoke separately.
Argument Modifiers
Argc uses suffix symbols to modify parameter behavior:
Basic modifiers:
-
"!" - Required parameter (must be provided)
-
"*" - Multi-value parameter (can be provided multiple times, becomes array)
-
"+" - Required multi-value (combines "!" and "*")
Examples:
@arg name! Required positional arg
@arg files* Optional multi-value (0 or more)
@arg tags+ Required multi-value (1 or more)
@option --config! Required option
@option --include* Multi-value option (can use multiple times)
Accessing multi-value parameters:
@arg files* Input files to process
main() { for file in "${argc_files[@]}"; do echo "Processing $file" done }
Rest-of-arguments pattern:
Use <VALUE+> in the last notation to capture "all remaining arguments":
@arg command! Command to run
@arg args* <ARGS+> All remaining arguments
main() { echo "Running: $argc_command ${argc_args[@]}" "$argc_command" "${argc_args[@]}" }
Run: ./script.sh docker run -it ubuntu bash
- captures run -it ubuntu bash into argc_args array.
Inline Choices and Default Values
Inline choices - Define valid values directly in the tag:
@arg env[dev|staging|prod] Environment choice
@option --format[json|yaml|toml] Output format
Default choice - Use = prefix to set default:
@arg env[=dev|staging|prod] Defaults to 'dev'
@option --format[=json|yaml|toml] Defaults to 'json'
Default values - Use =value syntax:
@arg name=world Defaults to 'world' if not provided
@option --timeout=30 Defaults to 30
@option --host=localhost Defaults to 'localhost'
When to use inline vs choice functions:
-
Use inline choices for static lists (3-10 items) known at script-writing time
-
Use choice functions for dynamic lists generated at runtime (files, git branches, API data)
Environment Variables
The @env tag validates and documents environment variables your script requires:
Basic syntax:
@env API_KEY! Required env var
@env DEBUG Optional env var
@env PORT=8080 Optional with default value
@env ENV[dev|prod] Env var with choices
@env REGION[=us|eu|asia] Env var with choices and default
Validation example:
#!/bin/bash
@describe API client
@env API_KEY! API authentication key
@env API_TIMEOUT=30 Request timeout in seconds
main() { echo "Using API_KEY: ${API_KEY:0:8}..." echo "Timeout: $API_TIMEOUT seconds" }
eval "$(argc --argc-eval "$0" "$@")"
If API_KEY is not set, argc generates an error before your code runs:
error: the following required environment variables were not provided: API_KEY
Use in Argcfile.sh tasks:
@cmd deploy Deploy application
@env DEPLOY_ENV![dev|staging|prod] Target environment
@env AWS_REGION=us-east-1 AWS region
deploy() { echo "Deploying to $DEPLOY_ENV in $AWS_REGION" }
Environment variables are accessed directly by name (not via argc_ prefix).
Choice Functions - Dynamic Validation
When to create choice functions:
Choice functions should be created almost always when the valid input values are known at runtime. They should always be created when input can be validated by checking against a generated list.
Basic pattern:
@arg file!_choice_files File to process
_choice_files() { ls examples/ }
How it works:
-
The backtick syntax
_choice_filestells argc to call this function -
Argc captures the function's stdout (one value per line)
-
Input is validated against this list
-
Invalid input triggers automatic error with possible values shown
Real-world examples:
Files in directory
_choice_files() { ls -1 "$EXAMPLE_FOLDER_PATH" }
Git branches
_choice_branches() { git branch --list --format='%(refname:short)' }
Docker containers
_choice_containers() { docker ps --format '{{.Names}}' }
GitHub repositories via gh CLI
_choice_repos() { gh repo list skogai --json nameWithOwner --jq ".[].nameWithOwner" }
API endpoints from config
_choice_endpoints() { jq -r '.endpoints[].name' config.json }
Key principle: If you can generate a list of valid values, create a choice function. This enables immediate validation and helpful error messages.
Symbols - Dynamic Choice Functions
Symbols let you use an earlier parameter to filter what values are valid for later parameters. This creates "pre-validation validation" where one choice constrains another.
Pattern:
@meta symbol +filetype[_choice_filetypes]
@arg files+,[_choice_files] Files to process
_choice_filetypes() { echo ".md" echo ".sh" }
_choice_files() { find ./src/ -type f -name "*${argc_filetype}" }
How it works:
-
@meta symbol +filetype defines + as a symbol that takes a filetype
-
User runs: argc +.md process file1.md file2.md
-
Argc sets argc_filetype=.md before validating files
-
_choice_files uses ${argc_filetype} to filter the find results
-
Only .md files are valid choices for files argument
The validation cascade:
With +.md - validates successfully
$ argc --argc-eval script.sh +.md process doc.md argc_filetype=.md argc_files=( doc.md ) process doc.md
With +.sh - validation fails because doc.md not in .sh file list
$ argc --argc-eval script.sh +.sh process doc.md
error: invalid value doc.md for <FILES+>...
[possible values: script.sh, build.sh, test.sh]
exit 1
Symbols make choice functions dynamic based on user input, enabling complex validation logic through pure declarations.
Task Runner Pattern (Argcfile.sh)
Argc doubles as a task runner - a bash-native alternative to Make. The Argcfile.sh pattern transforms bash functions into discoverable, documented CLI commands.
Basic Setup:
-
Create Argcfile.sh in the project root
-
Define tasks as bash functions with @cmd tags
-
Run tasks via argc <task-name>
Simple Example:
#!/bin/bash
@cmd build Build the project
build() { echo "Building..." }
@cmd test Run tests
test() { echo "Testing..." }
eval "$(argc --argc-eval "$0" "$@")"
Run: argc build or argc test
Task Runner Capabilities:
- Tasks with arguments:
@cmd deploy Deploy to environment
@arg env! Target environment (staging/production)
deploy() { echo "Deploying to $argc_env" }
- Tasks with options:
@cmd test Run test suite
@option --filter Test name pattern to match
@flag --verbose Show detailed output
test() { local args=() [[ -n "$argc_filter" ]] && args+=(--filter "$argc_filter") [[ "$argc_verbose" == "1" ]] && args+=(--verbose) pytest "${args[@]}" }
- Multi-namespace organization for large Argcfiles:
@cmd docker::build Build docker image
docker::build() { docker build -t myapp . }
@cmd docker::run Run docker container
docker::run() { docker run myapp }
@cmd k8s::deploy Deploy to kubernetes
k8s::deploy() { kubectl apply -f k8s/ }
Run with: argc docker::build , argc k8s::deploy
- Environment variable management:
@cmd connect Connect to database
@env DATABASE_URL! Database connection string
@env DATABASE_TIMEOUT=30 Connection timeout in seconds
connect() { echo "Connecting to $DATABASE_URL with ${DATABASE_TIMEOUT}s timeout" }
- Task dependencies (manual orchestration):
@cmd ci Run full CI pipeline
ci() { argc lint argc test argc build }
@cmd lint Run linters
lint() { echo "Linting..." }
Why use Argcfile.sh over Make:
-
Native bash syntax - full scripting power, no Make DSL
-
Automatic help generation and argument validation
-
Shell autocompletion for tasks and arguments
-
Environment variable integration
-
No tab-vs-spaces issues
-
Natural parameter passing (no Make variable gymnastics)
Behind the Scenes
Argc is not magic - it's bash code generation all the way down. To understand what argc --argc-eval actually does, look at what argc --argc-build generates: a standalone script with all parsing logic embedded.
The quick-start example (20 lines with comment tags) becomes 280+ lines when built standalone. Here's what argc generates:
Validation function - This is what validates choices from _choice_files() :
_argc_validate_choices() { local render_name="$1" raw_choices="$2" choices item choice concated_choices="" while IFS= read -r line; do choices+=("$line") done <<<"$raw_choices" for choice in "${choices[@]}"; do if [[ -z "$concated_choices" ]]; then concated_choices="$choice" else concated_choices="$concated_choices, $choice" fi done for item in "${@:3}"; do local pass=0 choice for choice in "${choices[@]}"; do if [[ "$item" == "$choice" ]]; then pass=1 fi done if [[ $pass -ne 1 ]]; then _argc_die "error: invalid value `$item` for $render_name"$'\n'" [possible values: $concated_choices]" fi done }
This function:
-
Takes the output from your choice function (_choice_files )
-
Reads it line-by-line into a bash array
-
Checks if the user's input matches any valid choice
-
If not, generates the error message with all possible values
-
Calls _argc_die to exit with error
Other generated functions:
-
_argc_parse()
-
Main argument parser with case statement for all flags/options
-
_argc_usage()
-
Help text generator (what you see with --help )
-
_argc_version()
-
Version display handler
-
_argc_take_args()
-
Multi-value argument collector
-
_argc_match_positionals()
-
Positional argument matcher
-
_argc_maybe_flag_option()
-
Flag/option detection
-
_argc_die()
-
Error handler and exit
-
_argc_run()
-
Entry point orchestrator
Key insight: When you run argc --argc-eval , it generates similar code but outputs it as text for eval instead of embedding it in the script. Same logic, different delivery mechanism.
Trade-offs:
-
Small script + argc binary: 20 lines + eval "$(argc --argc-eval ...)" (requires argc installed)
-
Standalone script: 280+ lines, no dependencies, works anywhere bash works
Full generated code: @references/quick-start-built.sh
Structured schema export:
Argc can also export the parsed CLI structure as JSON via argc --argc-export :
{ "name": "quick-start", "describe": "Example CLI tool", "flag_options": [ { "id": "verbose", "flag": true, "required": false, ... }, { "id": "file", "flag": false, "num_args": [1, 1], ... } ], "positionals": [ { "id": "file", "required": true, "choice": { "type": "Fn", "data": ["_choice_files", true] } } ], "envs": [ { "id": "EXAMPLE_FOLDER_PATH", "default": { "value": "examples/" } } ], "command_fn": "main" }
This machine-readable format captures everything argc extracted from the comment tags - useful for programmatic access, tooling integration, and AI agent automation.
Full export: @references/quick-start-export.json
Safety Guidelines
Never make argc scripts executable:
Do not use chmod +x on argc scripts. Instead, always execute them via:
argc --argc-run ./script.sh [args]
Or for Argcfile.sh task runners:
argc <task-name>
Note: Detailed explanation for this guideline will be provided separately.
Additional Resources
-
@docs/ - Original argc documentation from upstream repository (source of truth)
-
@examples/ - Original argc examples from upstream repository
Quick Start Template
- @examples/quick-start.sh
./examples/quick-start.sh --help # View generated help ./examples/quick-start.sh quick-start.sh # Basic usage ./examples/quick-start.sh quick-start.sh --verbose # With verbose flag ./examples/quick-start.sh idontexist.sh # Validation error EXAMPLE_FOLDER_PATH=/tmp/ ./examples/quick-start.sh claude -v # Override env var