# Custom Commands

Atmos can be easily extended to support any number of custom CLI commands. Custom commands are exposed through the `atmos` CLI when you run `atmos help`. It's a great way to centralize the way operational tools are run in order to improve DX.

_\[Video: Atmos Custom Commands]_

For example, one great way to use custom commands is to tie all the miscellaneous scripts into one consistent CLI interface. Then we can kiss those
ugly, inconsistent arguments to bash scripts goodbye! Just wire up the commands in atmos to call the script. Then developers can just run `atmos help`
and discover all available commands.

## Simple Example

Here is an example to play around with to get started.

Adding the following to `atmos.yaml` will introduce a new `hello` command.

```yaml
# Custom CLI commands
commands:
  - name: hello
    description: This command says Hello world
    steps:
      - "echo Hello world!"
```

We can run this example like this:

```shell
atmos hello
```

## Positional Arguments

Atmos also supports positional arguments. If a positional argument is required but not provided by the user,
the command will fail—unless you define a default in your config.

For the example, adding the following to `atmos.yaml` will introduce a new `greet` command that accepts one `name` argument,
but uses a default of "John Doe" if none is provided.

```yaml
# subcommands
commands:
  - name: greet
    description: This command says hello to the provided name
    arguments:
      - name: name
        description: Name to greet
        required: true
        default: John Doe
    steps:
      - "echo Hello {{ .Arguments.name }}!"
```

We can run this example like this:

```shell
atmos greet Alice
```

or defaulting to "John Doe"

```shell
atmos greet
```

## Trailing Arguments

Atmos supports **trailing arguments** after `--` (a standalone double-dash). The `--` itself is a delimiter that signals the end of Atmos-specific options. Anything after `--` is passed directly to the underlying command without being interpreted by Atmos. The value of these trailing arguments is accessible in `{{ .TrailingArgs }}`.

For the example, adding the following to `atmos.yaml` will introduce a new `echo` command that accepts one `name` argument and also uses trailingArgs

```yaml
- name: ansible run
  description: "Runs an Ansible playbook, allowing extra arguments after --."
  arguments:
    - name: playbook
      description: "The Ansible playbook to run"
      default: site.yml
      required: true
  steps:
    - "ansible-playbook {{ .Arguments.playbook }} {{ .TrailingArgs }}"
```

Output:

```bash
$ atmos ansible run -- --limit web
Running: ansible-playbook site.yml --limit web

PLAY [web] *********************************************************************

```

## Passing Flags

Passing flags works much like passing positional arguments, except for that they are passed using long or short flags.
Flags can be optional (this is configured by setting the `required` attribute to `false`).

```yaml
# subcommands
commands:
  - name: hello
    description: This command says hello to the provided name
    flags:
      - name: name
        shorthand: n
        description: Name to greet
        required: true
    steps:
      - "echo Hello {{ .Flags.name }}!"
```

We can run this example like this, using the long flag:

```shell
atmos hello --name world
```

Or, using the shorthand, we can just write:

```shell
atmos hello -n world
```

## Boolean Flags

Flags can be defined as boolean type using `type: bool`. Boolean flags don't require a value to be passed—when present, they are set to `true`.

```yaml
commands:
  - name: deploy
    description: Deploy to environment
    flags:
      - name: dry-run
        shorthand: d
        description: Perform a dry run without making changes
        type: bool
      - name: verbose
        shorthand: v
        description: Enable verbose output
        type: bool
        default: false
      - name: auto-approve
        description: Auto-approve without prompting
        type: bool
        default: true
    steps:
      - |
        {{ if .Flags.dry-run }}
        echo "DRY RUN MODE"
        {{ end }}
        {{ if .Flags.verbose }}
        echo "Verbose output enabled"
        {{ end }}
        {{ if .Flags.auto-approve }}
        terraform apply -auto-approve
        {{ else }}
        terraform apply
        {{ end }}
```

Usage:

```shell
# Enable dry-run (sets it to true)
atmos deploy --dry-run

# Use short flag
atmos deploy -d

# Boolean with explicit value
atmos deploy --auto-approve=false
```

### Using Boolean Flags in Steps

Boolean flags are available as Go template variables with values `true` or `false`. Here are common patterns for using them in bash:

```yaml
commands:
  - name: build
    description: Build the project
    flags:
      - name: verbose
        shorthand: v
        type: bool
        description: Enable verbose output
      - name: clean
        type: bool
        default: true
        description: Clean before building
    steps:
      # Pattern 1: Conditional command execution with if/else
      - |
        {{ if .Flags.verbose }}
        echo "Verbose mode enabled"
        set -x
        {{ end }}

      # Pattern 2: Inline conditional flag
      - echo "Building{{ if .Flags.verbose }} with verbose output{{ end }}..."

      # Pattern 3: Pass as flag to another command
      - make build {{ if .Flags.verbose }}VERBOSE=1{{ end }}

      # Pattern 4: Conditional step execution
      - |
        {{ if .Flags.clean }}
        echo "Cleaning build directory..."
        rm -rf ./build
        {{ end }}

      # Pattern 5: Negation check
      - |
        {{ if not .Flags.clean }}
        echo "Skipping clean step"
        {{ end }}

      # Pattern 6: Convert to shell variable
      - |
        VERBOSE={{ .Flags.verbose }}
        if [ "$VERBOSE" = "true" ]; then
          echo "Verbose is on"
        fi

      # Pattern 7: Using printf for explicit string conversion
      - |
        VERBOSE={{ printf "%t" .Flags.verbose }}
        echo "Verbose flag is: $VERBOSE"
```

:::tip
Boolean values automatically render as `true` or `false` (lowercase strings) when used in templates. You can reference them directly with `{{ .Flags.name }}`—no conversion needed!
:::

## Flag Defaults

Both string and boolean flags support default values using the `default` attribute:

```yaml
flags:
  - name: environment
    description: Target environment
    default: "development"
  - name: force
    type: bool
    description: Force the operation
    default: false
  - name: auto-approve
    type: bool
    description: Skip confirmation prompts
    default: true
```

When a flag has a default value, users can omit it from the command line. The default value will be used unless explicitly overridden.

## Advanced Examples

### Define a New Terraform Command

```yaml
# Custom CLI commands
commands:
  - name: terraform
    description: Execute 'terraform' commands
    # subcommands
    commands:
      - name: provision
        description: This command provisions terraform components
        arguments:
          - name: component
            description: Name of the component
        flags:
          - name: stack
            shorthand: s
            description: Name of the stack
            required: true
        # ENV var values support Go templates
        env:
          - key: ATMOS_COMPONENT
            value: "{{ .Arguments.component }}"
          - key: ATMOS_STACK
            value: "{{ .Flags.stack }}"
        steps:
          - atmos terraform plan $ATMOS_COMPONENT -s $ATMOS_STACK
          - atmos terraform apply $ATMOS_COMPONENT -s $ATMOS_STACK
```

### Override an Existing Terraform Command

```yaml
# Custom CLI commands
commands:
  - name: terraform
    description: Execute 'terraform' commands
    # subcommands
    commands:
      - name: apply
        description: This command executes 'terraform apply -auto-approve' on terraform components
        arguments:
          - name: component
            description: Name of the component
        flags:
          - name: stack
            shorthand: s
            description: Name of the stack
            required: true
        steps:
          - atmos terraform apply {{ .Arguments.component }} -s {{ .Flags.stack }} -auto-approve
```

### Show Component Info

```yaml
# Custom CLI commands
commands:
  - name: show
    description: Execute 'show' commands
    # subcommands
    commands:
      - name: component
        description: Execute 'show component' command
        arguments:
          - name: component
            description: Name of the component
        flags:
          - name: stack
            shorthand: s
            description: Name of the stack
            required: true
        # ENV var values support Go templates and have access to {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables
        env:
          - key: ATMOS_COMPONENT
            value: "{{ .Arguments.component }}"
          - key: ATMOS_STACK
            value: "{{ .Flags.stack }}"
          - key: ATMOS_TENANT
            value: "{{ .ComponentConfig.vars.tenant }}"
          - key: ATMOS_STAGE
            value: "{{ .ComponentConfig.vars.stage }}"
          - key: ATMOS_ENVIRONMENT
            value: "{{ .ComponentConfig.vars.environment }}"
        # If a custom command defines 'component_config' section with 'component' and 'stack', 'atmos' generates the config for the component in the stack
        # and makes it available in {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables,
        # exposing all the component sections (which are also shown by 'atmos describe component' command)
        component_config:
          component: "{{ .Arguments.component }}"
          stack: "{{ .Flags.stack }}"
        # Steps support using Go templates and can access all configuration settings (e.g. {{ .ComponentConfig.xxx.yyy.zzz }})
        # Steps also have access to the ENV vars defined in the 'env' section of the 'command'
        steps:
          - 'echo Atmos component from argument: "{{ .Arguments.component }}"'
          - 'echo ATMOS_COMPONENT: "$ATMOS_COMPONENT"'
          - 'echo Atmos stack: "{{ .Flags.stack }}"'
          - 'echo Terraform component: "{{ .ComponentConfig.component }}"'
          - 'echo Backend S3 bucket: "{{ .ComponentConfig.backend.bucket }}"'
          - 'echo Terraform workspace: "{{ .ComponentConfig.workspace }}"'
          - 'echo Namespace: "{{ .ComponentConfig.vars.namespace }}"'
          - 'echo Tenant: "{{ .ComponentConfig.vars.tenant }}"'
          - 'echo Environment: "{{ .ComponentConfig.vars.environment }}"'
          - 'echo Stage: "{{ .ComponentConfig.vars.stage }}"'
          - 'echo Dependencies: "{{ .ComponentConfig.deps }}"'
```

### Set EKS Cluster

```yaml
# Custom CLI commands
commands:
  - name: set-eks-cluster
    description: |
      Download 'kubeconfig' and set EKS cluster.

      Example usage:
        atmos set-eks-cluster eks/cluster -s plat-ue1-dev -r admin
        atmos set-eks-cluster eks/cluster -s plat-uw2-prod --role reader
    verbose: false  # Set to `true` to see verbose outputs
    arguments:
      - name: component
        description: Name of the component
    flags:
      - name: stack
        shorthand: s
        description: Name of the stack
        required: true
      - name: role
        shorthand: r
        description: IAM role to use
        required: true
    # If a custom command defines 'component_config' section with 'component' and 'stack',
    # Atmos generates the config for the component in the stack
    # and makes it available in {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables,
    # exposing all the component sections (which are also shown by 'atmos describe component' command)
    component_config:
      component: "{{ .Arguments.component }}"
      stack: "{{ .Flags.stack }}"
    env:
      - key: KUBECONFIG
        value: /dev/shm/kubecfg.{{ .Flags.stack }}-{{ .Flags.role }}
    steps:
      - >
        aws
        --profile {{ .ComponentConfig.vars.namespace }}-{{ .ComponentConfig.vars.tenant }}-gbl-{{ .ComponentConfig.vars.stage }}-{{ .Flags.role }}
        --region {{ .ComponentConfig.vars.region }}
        eks update-kubeconfig
        --name={{ .ComponentConfig.vars.namespace }}-{{ .Flags.stack }}-eks-cluster
        --kubeconfig="${KUBECONFIG}"
        > /dev/null
      - chmod 600 ${KUBECONFIG}
      - echo ${KUBECONFIG}
```

### List Stacks and Components

```yaml
# Custom CLI commands
commands:
  - name: list
    description: Execute 'atmos list' commands
    # subcommands
    commands:
      - name: stacks
        description: |
          List all Atmos stacks.
        steps:
          - >
            atmos describe stacks --process-templates=false --sections none | grep -e "^\S" | sed s/://g
      - name: components
        description: |
          List all Atmos components in all stacks or in a single stack.

          Example usage:
            atmos list components
            atmos list components -s tenant1-ue1-dev
            atmos list components --stack tenant2-uw2-prod
        flags:
          - name: stack
            shorthand: s
            description: Name of the stack
            required: false
        steps:
          - >
            {{ if .Flags.stack }}
            atmos describe stacks --stack {{ .Flags.stack }} --format json --sections none | jq ".[].components.terraform" | jq -s add | jq -r "keys[]"
            {{ else }}
            atmos describe stacks --format json --sections none | jq ".[].components.terraform" | jq -s add | jq -r "keys[]"
            {{ end }}
```

## Tool Dependencies

Custom commands can declare tool dependencies that are automatically installed before execution. This ensures that any CLI tools your command requires are available at the correct version, leveraging Atmos's [toolchain management](/cli/configuration/toolchain/) capabilities.

### Basic Usage

Specify tool dependencies using the `dependencies.tools` section within your command:

```yaml
commands:
  - name: lint
    description: Run tflint on terraform components
    dependencies:
      tools:
        tflint: "0.54.0"
    arguments:
      - name: component
        description: Component to lint
        required: true
    flags:
      - name: stack
        shorthand: s
        description: Stack name
        required: true
    steps:
      - atmos terraform generate varfile {{ .Arguments.component }} -s {{ .Flags.stack }}
      - tflint --chdir=components/terraform/{{ .Arguments.component }}
```

When you run `atmos lint vpc -s plat-ue2-dev`, Atmos will:

1. Check if tflint 0.54.0 is installed in the toolchain directory
2. Install it from the [toolchain registry](/cli/configuration/toolchain/registries) if missing
3. Execute the command steps with the tool available in PATH

### Multiple Tool Dependencies

Commands can declare multiple tool dependencies:

```yaml
commands:
  - name: security-scan
    description: Run security scans on infrastructure code
    dependencies:
      tools:
        tflint: "0.54.0"
        checkov: "3.0.0"
        tfsec: "1.28.0"
    steps:
      - tflint --chdir=components/terraform
      - checkov -d components/terraform
      - tfsec components/terraform
```

### Version Specification

Tool versions can be specified in several formats:

```yaml
dependencies:
  tools:
    # Exact version
    terraform: "1.9.8"

    # Latest available version
    kubectl: "latest"
```

### Integration with Toolchain Configuration

Tool dependencies in custom commands work with your toolchain configuration in `atmos.yaml`. Tools are installed to the configured `install_path` and resolved using your configured [registries](/cli/configuration/toolchain/registries).

```yaml
# atmos.yaml
toolchain:
  install_path: ".tools"
  registries:
    - name: aqua
      type: aqua
      source: https://github.com/aquaproj/aqua-registry/tree/main/pkgs
      priority: 10

commands:
  - name: lint
    dependencies:
      tools:
        tflint: "0.54.0"  # Installed to .tools/, resolved via aqua registry
    steps:
      - tflint --version
```

:::tip
Use tool dependencies in custom commands to ensure all team members and CI/CD pipelines use consistent tool versions without manual installation steps.
:::

## Working Directory

Custom commands can specify a `working_directory` field to control where the command steps execute. This is useful when commands need to run from a specific location, regardless of where `atmos` was invoked.

### Path Resolution

- **Absolute paths** are used as-is (e.g., `/tmp`, `/home/user/scripts`)
- **Relative paths** are resolved against the Atmos `base_path`
- The `!repo-root` YAML function can be used to reference the git repository root

### Example: Run from Repository Root

```yaml
commands:
  - name: build
    description: Build the project from repository root
    working_directory: !repo-root .
    steps:
      - make build
      - make test
```

This ensures the build commands run from the repository root, even if you invoke `atmos build` from a subdirectory.

### Example: Run in Temp Directory

```yaml
commands:
  - name: download-tools
    description: Download and extract tools in /tmp
    working_directory: /tmp
    steps:
      - wget https://example.com/tools.tar.gz
      - tar -xzf tools.tar.gz
```

### Example: Run in Component Directory

```yaml
commands:
  - name: component-init
    description: Initialize a component
    working_directory: components/terraform/vpc
    steps:
      - terraform init
      - terraform validate
```

Since `components/terraform/vpc` is a relative path, it will be resolved against `base_path`.

:::tip
Use `working_directory: !repo-root .` when defining commands in `.atmos.d/` at the repository root. This ensures commands work correctly when invoked from any subdirectory in your project.
:::

## Using Authentication with Custom Commands

Custom commands can specify an `identity` field to authenticate before execution. This is useful when commands need to interact with cloud resources that require specific credentials or elevated permissions.

When an identity is specified, Atmos will:

1. Authenticate using the specified identity (prompting for MFA if required)
2. Write temporary credentials to a file
3. Set environment variables pointing to the credential files (`AWS_SHARED_CREDENTIALS_FILE`, `AWS_CONFIG_FILE`, `AWS_PROFILE`, etc.)
4. Execute all command steps with these environment variables

### Example: Custom Command with Authentication

```yaml
commands:
  - name: deploy-infra
    description: Deploy infrastructure with superadmin privileges
    identity: superadmin  # Authenticate as superadmin before running
    arguments:
      - name: component
        description: Component to deploy
        required: true
    flags:
      - name: stack
        shorthand: s
        description: Stack to deploy to
        required: true
    steps:
      - atmos terraform plan {{ .Arguments.component }} -s {{ .Flags.stack }}
      - atmos terraform apply {{ .Arguments.component }} -s {{ .Flags.stack }} -auto-approve
```

To execute this command:

```shell
atmos deploy-infra vpc -s plat-ue2-prod
```

Atmos will:

1. Authenticate as `superadmin` (prompting for MFA if configured)
2. Set up environment variables pointing to temporary credential files
3. Execute both `terraform plan` and `terraform apply` with those credentials

### Example: Multi-Step Command with Authentication

```yaml
commands:
  - name: audit-resources
    description: Audit cloud resources with auditor credentials
    identity: auditor
    flags:
      - name: region
        shorthand: r
        description: AWS region
        required: true
    steps:
      - |
        echo "Running audit in {{ .Flags.region }}..."
        aws sts get-caller-identity
      - |
        aws ec2 describe-instances --region {{ .Flags.region }} \
          --query 'Reservations[].Instances[].[InstanceId,State.Name,Tags[?Key==`Name`].Value|[0]]' \
          --output table
      - |
        aws s3 ls --region {{ .Flags.region }}
```

All steps in this command will execute with the `auditor` identity credentials.

### Authentication with Component Config

You can combine identity authentication with component configuration:

```yaml
commands:
  - name: provision-with-auth
    description: Provision component with specific identity
    identity: infrastructure-admin
    arguments:
      - name: component
        description: Component to provision
    flags:
      - name: stack
        shorthand: s
        description: Stack name
        required: true
    component_config:
      component: "{{ .Arguments.component }}"
      stack: "{{ .Flags.stack }}"
    env:
      - key: COMPONENT_REGION
        value: "{{ .ComponentConfig.vars.region }}"
    steps:
      - |
        echo "Provisioning {{ .Arguments.component }} in {{ .ComponentConfig.vars.region }}"
        echo "Using identity: infrastructure-admin"
        aws sts get-caller-identity
      - atmos terraform apply {{ .Arguments.component }} -s {{ .Flags.stack }}
```

:::tip
Configure identities in your [`atmos.yaml`](/cli/configuration) under the `auth` section. See the [Authentication documentation](/cli/commands/auth/usage) for configuration details.
:::

### Overriding Identity at Runtime

You can override the identity specified in the command configuration using the `--identity` flag:

```shell
# Use the identity from command config
atmos deploy-infra vpc -s plat-ue2-prod

# Override with a different identity
atmos deploy-infra vpc -s plat-ue2-prod --identity developer

# Use no identity (skip authentication even if configured)
atmos deploy-infra vpc -s plat-ue2-prod --identity ""
```

The `--identity` flag is automatically added to all custom commands and takes precedence over the `identity` field in the command configuration.

:::note
The `identity` field applies to all steps in the custom command. If you need different identities for different operations, consider using separate custom commands or [workflows](/workflows) with per-step identity configuration.
:::

## Extended Step Types

Custom commands support the same extended step types as workflows, enabling interactive CLI wizards directly in your custom commands. Use these step types to collect user input, display formatted output, and control execution flow.

### Example: Interactive Deployment Command

```yaml
commands:
  - name: deploy-wizard
    description: Interactive deployment wizard
    steps:
      # Collect environment selection
      - name: env
        type: choose
        prompt: "Select target environment"
        options:
          - dev
          - staging
          - prod
        default: dev

      # Show warning for production
      - name: prod_warning
        type: warn
        content: "You are about to deploy to PRODUCTION!"

      # Confirm deployment
      - name: confirm
        type: confirm
        prompt: "Deploy to {{ .steps.env.value }}?"
        default: false

      # Run the deployment
      - name: deploy
        type: atmos
        command: terraform apply vpc -s {{ .steps.env.value }}

      # Show success message
      - name: done
        type: success
        content: "Deployment to {{ .steps.env.value }} completed!"
```

### Example: Multi-Component Deploy with Selection

```yaml
commands:
  - name: deploy-components
    description: Select and deploy multiple components
    steps:
      # Multi-select components
      - name: components
        type: filter
        prompt: "Select components to deploy"
        multiple: true
        options:
          - vpc
          - eks
          - rds
          - s3
          - lambda

      # Show selected components
      - name: summary
        type: markdown
        content: |
          ## Deployment Summary

          You selected **{{ len .steps.components.values }}** components:
          {{ range .steps.components.values }}
          - {{ . }}
          {{ end }}

      # Confirm and deploy
      - name: confirm
        type: confirm
        prompt: "Proceed with deployment?"

      # Deploy each component
      - type: shell
        command: |
          {{ range .steps.components.values }}
          echo "Deploying {{ . }}..."
          {{ end }}
```

### Example: Input Collection

```yaml
commands:
  - name: create-ticket
    description: Create a deployment ticket
    steps:
      - name: title
        type: input
        prompt: "Ticket title"
        placeholder: "Brief description of the change"

      - name: description
        type: write
        prompt: "Detailed description"

      - name: priority
        type: choose
        prompt: "Priority level"
        options: [low, medium, high, critical]
        default: medium

      - type: success
        content: |
          Ticket created:
          Title: {{ .steps.title.value }}
          Priority: {{ .steps.priority.value }}
```

### Available Step Types Reference

## Using Toolchain Tools

Custom commands automatically have access to tools defined in your project's `.tool-versions` file. The toolchain ensures the correct versions are installed and available in PATH when your command executes.

### Automatic Tool Access

If your project has a `.tool-versions` file:

```
terraform 1.10.0
kubectl 1.32.0
helm 3.16.0
```

Custom commands can use these tools directly:

```yaml
commands:
  - name: deploy
    description: Deploy infrastructure
    steps:
      - terraform init
      - terraform plan
      - kubectl apply -f manifests/
```

When you run `atmos deploy`:

1. Atmos reads `.tool-versions` to identify required tools
2. Missing tools are automatically installed
3. PATH is updated to include toolchain binaries
4. Command steps execute with the correct tool versions

### Declaring Additional Dependencies

Use the `dependencies` field to require additional tools or override versions from `.tool-versions`:

```yaml
commands:
  - name: validate
    description: Validate with specific tool versions
    dependencies:
      tools:
        tflint: "^0.54.0"      # Additional tool not in .tool-versions
        terraform: "1.9.8"     # Override .tool-versions version
    steps:
      - terraform validate
      - tflint --recursive
```

Dependencies declared in the command take precedence over `.tool-versions`.

### Version Constraints

Dependencies support SemVer constraints:

| Constraint | Meaning | Example |
|------------|---------|---------|
| `1.10.3` | Exact version | Only version 1.10.3 |
| `~> 1.10.0` | Pessimistic (patch) | Includes 1.10.x but not 1.11.0 |
| `^1.10.0` | Compatible (minor) | Includes 1.x.x but not 2.0.0 |
| `latest` | Latest available | Most recent version |

:::tip
The combination of `.tool-versions` for project-wide defaults and per-command `dependencies` for overrides gives you flexibility while maintaining consistency across your team.
:::

### Related Documentation

- [Toolchain Configuration](/cli/configuration/toolchain) - Configure tool version management
- [Workflows](/workflows) - Workflows also support toolchain integration

## Custom Component Types

Custom commands can define their own component types beyond the built-in `terraform`, `helmfile`, and `packer` types. This enables you to use Atmos's stack configuration system for any tool—Ansible playbooks, Kubernetes manifests, shell scripts, CDK apps, and more.

### How It Works

When you define a `component:` section in a custom command with a `type`, Atmos:

1. Registers the custom component type in the component registry
2. Looks up component configuration from `components.<type>.<component>` in your stack manifests
3. Makes the component configuration available via `{{ .Component.* }}` template variables

### Typed Arguments and Flags

To tell Atmos which argument or flag provides the component name and stack name, use semantic types:

- **Arguments**: Set `type: component` or `type: stack` in the argument definition
- **Flags**: Set `semantic_type: component` or `semantic_type: stack` in the flag definition

Note: For flags, we use `semantic_type` because `type` is already used to specify the data type (`string`, `bool`).

### Example: Script Runner

Here's a complete example of a custom command that runs script components:

**atmos.yaml:**

```yaml
commands:
  - name: script
    description: "Run script components"
    arguments:
      - name: component
        description: "Component name"
        type: component              # This argument provides the component name
        required: true
    flags:
      - name: stack
        shorthand: s
        description: "Stack name"
        semantic_type: stack         # This flag provides the stack name
        required: true
    component:
      type: script                   # Define the custom component type
      base_path: components/script   # Optional: defaults to components/<type>
    steps:
      - 'echo "Running {{ .Component.component }} in {{ .Component.atmos_stack }}"'
      - 'echo "App: {{ .Component.vars.app_name }}"'
      - 'echo "Version: {{ .Component.vars.version }}"'
      - 'cd {{ .Component.vars.script_dir }} && ./deploy.sh'
```

**stacks/catalog/script/deploy-app.yaml:**

```yaml
components:
  script:                          # Matches component.type in the command
    deploy-app:                    # Component name
      vars:
        app_name: "myapp"
        version: "1.0.0"
        script_dir: "${atmos.base_path}/components/script/deploy-app"
```

**stacks/deploy/dev.yaml:**

```yaml
import:
  - catalog/script/deploy-app

vars:
  stage: dev

components:
  script:
    deploy-app:
      vars:
        version: "1.0.0-dev"      # Override for dev environment
```

**Usage:**

```shell
atmos script deploy-app -s dev
# Output:
# Running deploy-app in dev
# App: myapp
# Version: 1.0.0-dev
# (runs deploy.sh)
```

### Available Template Variables

When using custom component types, the `{{ .Component }}` object contains:

| Variable | Description |
|----------|-------------|
| `{{ .Component.component }}` | Component name |
| `{{ .Component.component_type }}` | Component type (e.g., "script") |
| `{{ .Component.atmos_stack }}` | Stack name |
| `{{ .Component.vars.* }}` | Variables defined in the component |
| `{{ .Component.settings.* }}` | Settings defined in the component |
| `{{ .Component.env.* }}` | Environment variables defined in the component |

### Configuration Options

The `component:` section supports:

| Option | Description |
|--------|-------------|
| `type` | **Required.** The component type name (e.g., "script", "ansible", "manifest") |
| `base_path` | Optional. Base directory for components of this type. Defaults to `components/<type>` |

### Comparison with component\_config

The `component:` section differs from the legacy `component_config:` in several ways:

| Feature | `component:` (new) | `component_config:` (legacy) |
|---------|-------------------|------------------------------|
| Component type | Custom types | Terraform only |
| Component/stack source | Inferred from typed args/flags | Explicit templates |
| Template variable | `{{ .Component.* }}` | `{{ .ComponentConfig.* }}` |
| Stack location | `components.<type>.<component>` | `components.terraform.<component>` |

:::tip
Use `component:` for new custom commands. The `component_config:` approach continues to work for backward compatibility but is limited to Terraform components.
:::

### Example: Ansible Playbook Runner

```yaml
commands:
  - name: ansible
    description: "Run Ansible playbooks"
    arguments:
      - name: component
        type: component
        required: true
    flags:
      - name: stack
        shorthand: s
        semantic_type: stack
        required: true
      - name: check
        description: "Run in check mode (dry-run)"
        type: bool
    component:
      type: ansible
      base_path: components/ansible
    steps:
      - |
        cd {{ .Component.vars.playbook_dir }}
        ansible-playbook {{ .Component.vars.playbook }} \
          -i {{ .Component.vars.inventory }} \
          --extra-vars "env={{ .Component.vars.environment }}" \
          {{ if .Flags.check }}--check{{ end }}
```

### Example: Kubernetes Manifest Deployer

```yaml
commands:
  - name: manifest
    description: "Apply Kubernetes manifests"
    arguments:
      - name: component
        type: component
        required: true
    flags:
      - name: stack
        shorthand: s
        semantic_type: stack
        required: true
      - name: dry-run
        type: bool
    component:
      type: manifest
    steps:
      - |
        kubectl apply \
          --context {{ .Component.vars.cluster_context }} \
          --namespace {{ .Component.vars.namespace }} \
          {{ if index .Flags "dry-run" }}--dry-run=client{{ end }} \
          -f {{ .Component.vars.manifest_path }}
```

## Try It

Explore working examples that demonstrate custom commands in action.
