Atmos Custom Commands
Use this skill when users define or modify project-specific Atmos CLI commands in the top-level
commands section of atmos.yaml.
Custom commands replace scattered scripts with discoverable CLI commands that can use arguments, flags, environment variables, authentication identities, tool dependencies, component config, nested subcommands, and typed execution steps.
When a task is mainly about steps, step type, working_directory, step env, output,
scripts, workdirs, or hook-compatible step payloads, also load atmos-steps.
Quick Shape
commands:- name: hellodescription: Say helloarguments:- name: namedefault: Worldsteps:- type: saytext: "Hello {{ .Arguments.name }}"
atmos helloatmos hello Erik
For full field syntax, read references/command-syntax.md.
Agent Workflow
- Inspect existing
commandsinatmos.yamland imported config before adding a new command. - Prefer native typed steps over large shell blocks.
- Add flags and arguments with clear names and defaults.
- Use
dependencies.toolsfor tools the command needs in its execution context. - Use
identitywhen the command needs Atmos Auth credentials. - Verify with
atmos <command> --helpand a dry-run or read-only invocation when possible.
Native Step Preference
Default to structured steps before shell:
| Need | Prefer |
|---|---|
| Run an Atmos command | type: atmos |
| Operator messages | say, toast, markdown, table, pager |
| Data shaping | format, join, filter, write |
| Status/progress | spin, stage, log, linebreak |
| Concurrency | parallel, matrix, wait, wait-all |
| Containers/emulators | container, emulator |
| HTTP calls | http |
| Preconditions | require / assert |
| External command glue | shell / exec |
Shell is still appropriate for short glue commands, terminal-native tools, checked-in scripts, or commands that genuinely need shell semantics.
Common Patterns
Flags and Arguments
commands:- name: deploy-onearguments:- name: componentrequired: trueflags:- name: stackshorthand: srequired: truesteps:- type: atmoscommand: terraform deploy {{ .Arguments.component }} -s {{ .Flags.stack }}
Flag type is string (default), bool, or int. An int flag registers as an integer flag
(--count int in help), its default must be a whole number, and it reaches templates and
ctx.flags as an integer. Any other type fails when Atmos loads the command, with an error that
lists the supported types. An int flag reads base-10 only (010 is 10, 0x10 is rejected). A default that does
not match the flag type makes the command a stub with a warning that names the flag. An argument with neither required: nor default: is required; set required: false to make it optional, and an
omitted optional argument with no default is an empty string in {{ .Arguments.<name> }} and ctx.arguments. Argument values keep
commas, empty strings, and non-ASCII text intact.
Tool Dependencies
commands:- name: scandependencies:tools:checkov: "latest"steps:- type: shellcommand: checkov --directory .
Authentication
commands:- name: prod-whoamidependencies:tools:aws-cli: "^2.0.0"identity: prod-readonlysteps:- type: shellcommand: aws sts get-caller-identity
Custom Component Types
Use component.type and semantic provides bindings when a custom command should
resolve a custom component and stack. component_config is legacy Terraform syntax.
commands:- name: render-apparguments:- name: componentprovides: componentrequired: trueflags:- name: stackshorthand: sprovides: stackrequired: truecomponent:type: applicationsteps:- type: shellcommand: ./scripts/render-app.sh
An embedded Starlark script step in such a command reads the selected component as
ctx.component (resolved on first access). Workflows have no component in scope, so
ctx.component is None there; see atmos-starlark.
Starlark Script Steps
Use a type: script step with interpreter: starlark when a command needs loops,
conditionals, data shaping, or concurrency that native steps cannot express. Inputs
reach the script as values, not as text:
commands:- name: deploy-onedescription: Deploy one componentarguments:- name: grouprequired: trueflags:- name: dry-runtype: bool- name: stackshorthand: srequired: truesteps:- name: deploytype: scriptinterpreter: starlarkscript: |if ctx.flags["dry-run"]:ui.info("dry run for " + ctx.arguments["group"])else:atmos.terraform("deploy", ctx.arguments["group"], ctx.flags["stack"])
- Read declared flags and arguments from
ctx.flags(string, bool, andinttypes preserved) andctx.arguments(an omitted optional argument is"").ctx.argsis empty, and trailing arguments after--are not exposed to scripts. - Read flag values from
ctx.flagsandctx.argumentsinstead of templating them into script source. Atmos renders valid{{ ... }}template actions before running the script; malformed or unresolved actions can fail. A lone}}is ordinary text. Usescript: !literal |or!include.rawwhen the source must remain unchanged. cli.command,cli.arg, andcli.flagare only for standalone scripts (atmos ./tool.star); declare the command interface in YAML here.- Script steps default to raw output with no step labels;
show: {labels: true}restores them.output:must beraw,log,viewport, ornone; any other value (such ascapture) fails before the step runs (capture/streamerrors point toexec.run, which takesstream|capture|viewport). - A
timeout:on a script, shell, or atmos step cancels the step and fails withstep timed out(Step '<name>' (type <t>) did not finish within its timeout of <d>). On Unix, commands without terminal input also terminate their process group. Commands with terminal input and Windows commands terminate only the direct child; descendants may remain running. To time an individual task, runsteps.parallel(tasks=[steps.task(name="work", function=work, timeout="30s")]). This task timeout coverswork, not the surrounding script. - Atmos renders only the values declared under the step's
env:as templates. The ambient process environment reaches the script and its child processes verbatim, so an inheritedFOO='{{ bad'is harmless. Inctx-side Starlark,envholds only the step's ownenv:entries, not command-levelenv:entries. - Sprig and Gomplate functions work the same in sequential steps and in
parallel/matrixchildren. - A script step's dict or list
outputreaches later steps as JSON text ({{ .steps.<name>.value }}is{"n":3}), never Go map syntax. Single-quote it in shell commands (echo '{{ .steps.x.value }}') because JSON contains double quotes. - A template error names the step and field, and the source file for an
!included script, with a hint to tag the field!literalwhen the body contains{{(for an!included script, the hint says to move the text into aload()ed module or use!include.raw, which is used as written).!literalalso works oncommand,interpreter,working_directory,timeout, and singleenvvalues, including command-levelenv; any other step field fails config load.retry.conditionsworks for shell and atmos steps too. --profileand--identityreach nestedatmos.*calls from script steps. Ctrl-C runsdefercalls (30 s grace) and exits 130 silently.
Routing
| Need | Skill |
|---|---|
| Complete command schema and examples | references/command-syntax.md |
| Reusable multi-step orchestration | atmos-workflows |
| Shared step fields and step types | atmos-steps |
Embedded Starlark, parallel function calls, and standalone atmos ./tool.star CLI apps | atmos-starlark |
| Smoke tests, integration tests, and test groups | atmos-tests |
| Tool versions and PATH behavior | atmos-toolchain |
| Auth providers, identities, assume role/root, OIDC | atmos-auth |
| Components and component inheritance | atmos-components |
| Go templates and YAML functions | atmos-templates, atmos-yaml-functions |
Guardrails
- Do not override built-in commands unless the user explicitly wants that behavior.
- Do not hide complex business logic inside inline YAML shell blocks. Move it to a checked-in script or use native step types.
- Do not use custom commands for long-lived reusable orchestration when an Atmos workflow is a better fit.
- Keep command names stable; they become user-facing CLI API.