standalone-scripts.md8.7 KB
View on GitHubStandalone Scripts and cli.command
A standalone script is a .star file (or an executable with an Atmos shebang) that Atmos
runs directly as its own command-line tool. This is "interpreter mode". See also
Custom CLI apps and the
function reference.
Running a script
atmos ./deploy.star vpc --stack=dev # Explicit path.atmos deploy.star vpc --stack=dev # A .star file needs no ./ prefix../deploy vpc --stack=dev # Executable with an Atmos shebang.
Rules:
.starfiles: any first argument ending in.starselects script mode. The file must exist and be a regular file. Only.starnames can produce a script error (a.stardirectory fails with "must be a regular file").- Extensionless files: the path must contain a separator (
./deploy,/opt/tools/deploy) and the first line must be an Atmos shebang. A bare name such asatmos deployis still a normal Atmos command lookup and fails as an unknown command. A path that is not a script (an existing directory such as./stacks, a missing file, a file without the shebang) falls through to normal command handling. - The
.starextension is optional when the script runs by path or through its shebang. It is what lets editors and GitHub recognize the file as Starlark. For extensionless files, put the shebang on line 1 and a file-type hint on line 2, for example# vim: set filetype=bzl: -*- mode: bazel-starlark -*-(Vim has nostarlarkfiletype; Emacs needs the bazel-mode package forbazel-starlark). GitHub Linguist only mapsft=/mode:values ofstarlarkorbazel, so use.gitattributes(bin/tool linguist-language=Starlark) for extensionless files there. The hint line does not affect shebang detection. - Accepted shebangs:
#!/usr/bin/env atmos,#!/usr/bin/env -S atmos, and an absolute path whose last element isatmos(#!/usr/local/bin/atmos). Other forms are not detected.atmosmust be onPATHfor theenvforms. - Symlinks are resolved before running.
ctx.script.pathis the real file, andload()resolves relative to the real file's directory, so a symlink onPATHcan load its sibling modules. - Atmos global flags go between
atmosand the script path, as--flag=valueor--flag value:atmos --chdir=x deploy.star,atmos --logs-level Debug ./deploy.star. The first word that is not a global flag starts the script; everything after it belongs to the script, including flags that look like Atmos flags. A leading--ends the global flags. A relative script path resolves after--chdiris applied. Flags with an optional value (--identity) must use--flag=value. An unknown flag before the script is not a script invocation and fails like any other unknown flag. - With
ATMOS_USE_VERSIONorversion.use, the re-exec forwards the script path and its arguments unchanged. atmoswith no arguments keeps the normal Atmos screen.- Atmos configuration (
atmos.yaml) is discovered from the current working directory with the usual search rules, not from the script's directory. A script that does not touch stacks or components runs with noatmos.yamlat all.components.get,atmos.terraform, and other project-aware calls need an Atmos project in or above the working directory. - Scripts are not rendered as Go templates;
{{in a standalone script is ordinary text. - Standalone scripts do not print the Atmos resource-usage summary. Set
settings.metrics.enabled: trueinatmos.yamlto opt in. ctx.script.pathandctx.script.directoryidentify the physical file.load()paths are relative to that file. Process working directory stays with the caller.- Top-level
outputis written to stdout (a string raw, anything else as compact JSON).printalso writes to stdout;ui.*writes to stderr.
Inputs
Standalone scripts get their inputs from cli.command callbacks or from raw ctx.args:
ctx.args: immutable list of all script arguments exactly as typed, including flags.main(args, flags)callback: parsedargsandflagsdicts (see below).ctx.flags,ctx.arguments, andenvare empty in standalone scripts. Those are for script steps in workflows and custom commands.
With no cli.command, handle ctx.args yourself. Prefer cli.command: it gives native
--help, typed flags, validation, and error messages.
Declaring the interface
def main(args, flags):print(args["service"], flags["replicas"], flags["dry-run"], flags["tag"])def validate(args, flags):if flags["replicas"] < 1:fail("--replicas must be at least 1")cli.command(run = main,name = "deploy",description = "Deploy a service",args = [cli.arg("service", description="Service name")],flags = [cli.flag("replicas", type="int", default=2, shorthand="r", description="Replica count"),cli.flag("dry-run", type="bool", shorthand="d"),cli.flag("tag", type="string_list", env="DEPLOY_TAGS"),cli.flag("env", choices=["dev", "prod"], default="dev"),],validate = validate,)
cli.command(run, name=, description=, args=, flags=, validate=):
runis required and called asrun(args, flags)with immutable dicts.cli.commandreturns whatrunreturns (andNonefor--help), sooutput = cli.command(...)emits that value. ANoneresult means no output: nothing prints for--helpor whenrunreturns nothing. For ordinary output, callprintinsiderun.name(default: the script file name) must start with a letter and contain only letters, digits,_, and-.descriptionappears in help.validate(args, flags)is optional. Callfail("message")or returnFalseto reject input. ReturningNoneorTrueaccepts it; any other return value is an error.- Call
cli.commandonce, from the main thread. A second call, or a call inside asteps.paralleltask, fails. It is unavailable in script steps (workflows, custom commands, hooks); usectx.flagsandctx.argumentsthere.
cli.arg(name, description=, required=True):
- Names must start with a letter and contain only letters, digits,
_, and-. - Argument names must be unique ignoring case, and required arguments must come before optional ones.
- A missing optional argument is
Nonein theargsdict.
cli.flag(name, type="string", default=, shorthand=, description=, required=False, choices=, env=):
typeis"string","int","bool", or"string_list".required=Truecannot have adefault. Aboolflag cannot be required; use a default.choicesworks only forstringandstring_list.- Flag names must be unique ignoring case (
Stageandstagecollide and are rejected). shorthandis one letter and cannot beh. Shorthands must be unique.helpis reserved (and-h). Every script gets--helpautomatically.envbinds an environment variable used when the flag is not given on the command line.
Parsing behavior to know
--helpor-hprints the help and skips bothvalidateandrun. Help marks required flags(required), lists choices as(one of: dev, prod), and shows env bindings as[env: NAME]. Top-level code still runs before help, so keep side effects (network calls, deployments, file writes) insiderun, never at the top level.- User input mistakes (unknown flag, missing or extra positional, invalid value, missing
required flag, failed
choices) are usage errors: no Starlark traceback, a hintRun <script> --help for usage., the usage line, and exit status 2. A missing required positional names the argument. They fail beforevalidateandrun. Errors from the script itself (fail(), runtime errors,validatereturningFalse) keep the Starlark traceback presentation. --ends flag parsing. Tokens after it are still validated as declared positional arguments; there is no variadic or trailing-argument capture. For free-form arguments, readctx.args.- A
boolflag takes no value token.--dry-run falseis rejected with a hint to write--dry-run=false, becausefalsewould otherwise become a positional argument. Put--before a positional that must be literallytrueorfalse. intvalues (command line and env) are base 10 only:--n 010is 10, and0x10is an error.string_listflags accumulate repeated flags and split a single value on commas with CSV quoting (--tag a,b --tag cgives["a", "b", "c"]). Anenvbinding parses exactly the same way (DEPLOY_TAGS=a,b); whitespace is not a separator. Withchoices, every element is validated.- Command-line values take precedence over
env, which takes precedence overdefault.