Skip to content

chant import

llms.txt lists every page for an agent
chant import <template-file> [flags]
chant import --from <env> [flags]
chant import --kustomize <dir> [flags]
chant import --agents [flags]

chant import reads an existing infrastructure template (e.g. a CloudFormation JSON template), detects which lexicon handles it, and generates TypeScript resource definitions. Use it to adopt chant one template at a time.

With --from <env> it reads the live cloud/cluster instead of a file: it resolves the environment, calls each project lexicon’s exportResources() for full-fidelity config, and generates TypeScript from that. Use it to bring an orphaned resource back into source.

With --kustomize <dir> it renders a kustomization first and imports the rendered manifests through the k8s lexicon. It runs kustomize build <dir>, falling back to kubectl kustomize <dir> when the standalone binary is absent. The flag names the lexicon, so no template detection happens. A rendering error exits nonzero with the first line of the tool’s message.

The import process:

  1. Reads and parses the template file: JSON first, then YAML. A YAML file may hold several documents. A line holding only --- starts one and a line holding only ... ends one, and either may carry a trailing # comment. Each document is parsed on its own, and a document whose top level is a list parses as that list
  2. Asks each lexicon plugin if it recognizes the template via detectTemplate(), one parsed document at a time in file order. The first document a lexicon recognizes decides
  3. Hands the file’s raw content to the matching lexicon’s templateParser(), which extracts resources into an intermediate representation
  4. Uses the lexicon’s templateGenerator() to produce TypeScript files
  5. Organizes generated files by resource category (storage, compute, network, other)

The template may be JSON or YAML, so Kubernetes manifests import the same way.

Import then builds the files it wrote and runs the lexicon’s post-synth checks, filtered by the project’s lint.presets and lint.rules as in chant build. Each error-severity finding is printed as a warning that starts with the check id. For example, WK8005: names a literal value in a sensitive environment variable. The first chant build stops on such an error, so change the generated source or turn the rule off. If the generated source does not build in place, for example because the lexicon is not installed beside it, import names nothing.

Content that belongs to another lexicon is imported by that lexicon. A ConfigMap holding a collector config, a Prometheus rule file or a Grafana dashboard, and a PrometheusRule’s spec.groups, come out as otel, prometheus or grafana declarations. An alertmanager.yml in a ConfigMap comes out as prometheus declarations too. Each is written in a directory of its own, named after the resource, and the k8s resource refers to them, e.g. data: { "config.yaml": collectorYaml([otlp, batch, debug, traces]) }. The owning lexicon only has to be installed. When it is not, the content stays a string and a warning names the package to install. A v2 Grafana dashboard also stays a string, with a warning, because the grafana lexicon builds v1 JSON. See Embedded content.

Detection asks the project’s lexicons first. These come from chant.config.ts or from the lexicons the project’s source imports. If none of them recognizes the template, or you run the command outside a project, every installed @intentius/chant-lexicon-* package is asked, in name order. A package counts as installed when chant can load it: it sits in a node_modules/@intentius directory above chant’s own install or above the project. So a Kubernetes manifest imported in an empty directory is detected as k8s if that lexicon is installed. Where several installed lexicons recognize the template, one that has a template parser goes first, and the others are named in a warning.

The output names the lexicon: Detected lexicon: <name> when detection chose it, Lexicon: <name> when you named it with --lexicon (or --kustomize, which implies k8s).

Pass --lexicon <name> to skip detection, for example when no lexicon recognizes the template. The raw file content then goes to that lexicon’s parser, which decides what it accepts. Without --lexicon, a file that is neither JSON nor YAML fails with Template is neither valid JSON nor YAML.

A lexicon’s template parser can take options. The lexicon declares them with parserOptions(). --parser-option passes them through on a template import, for a lexicon named with --lexicon and for a detected one alike. A name the lexicon does not declare fails the import before anything is parsed, with a message listing what the lexicon accepts. A lexicon that declares none refuses every --parser-option.

Terminal window
chant import dashboard.json --lexicon grafana --parser-option acceptLossyV1

--parser-option does not apply to --from (live import) or --agents. Grafana’s options are listed in Importing.

FlagDescription
--from <env>Import from a live environment instead of a template file
--kustomize <dir>Render the kustomization at dir and import the output as Kubernetes manifests. Needs kustomize or kubectl on the path
--parser-option <key[=value]>Pass an option to the lexicon’s template parser. Repeatable. Each lexicon lists the options it takes; any other name is refused. A boolean option can be given bare (--parser-option acceptLossyV1) or as =true / =false
-d, --lexicon <name>With a template file, import with this lexicon instead of detecting one. With --from, restrict to one lexicon (e.g. aws, k8s)
--type <ResourceType>With --from, export only resources of this type
--name <name>With --from, export only the resource with this name
--ownedWith --from, restrict to chant-owned resources — those carrying the live ownership marker
--verbatimWith --from, keep server-defaulted fields instead of stripping to declared shape
--skip-undeclarableWith --from, import what can be declared and warn about each live object that cannot be (the sql lexicon fails the import on them otherwise)
-o, --output <dir>Output directory (default: ./infra/)
--forceOverwrite existing files
--agentsImport this machine’s agent configuration instead of a template file (see below)
--scope <list>--agents only: comma-separated system, user, project (default: all)
--runtime <list>--agents only: comma-separated claude, codex, gemini, opencode, cursor (default: all)
--all-projects--agents only: import every project registered in ~/.claude.json, not just the path argument

chant import --agents is the other half of chant audit --agents: where the audit reports on the agent configuration found on a machine, this writes it as chant code, so a setup built up by hand can be reviewed, committed and rebuilt on another machine.

Terminal window
chant import --agents # → ./infra/agents/
chant import --agents --scope user -o infra/agents/ # just the home directory

The default target is the fountain lexicon, whose Agent models the same four ideas the harnesses do:

local agent configfountain
CLAUDE.md / AGENTS.mdAgent.system
mcpServers / mcp_serversAgent.mcp_servers
skills/*/SKILL.mdAgent.skills
settings envEnvironment.env_vars

Three things are changed on the way rather than transcribed:

  • Secrets are not carried over. A literal credential found in local config, in env or in MCP headers as a bearer token, is rewritten to a ${VAR} reference. Generated code gets committed, and a live secret in it would turn a local mistake into a repository one.
  • Egress is derived. A local agent has the machine’s full network access, and a fountain sandbox must declare intent. The hosts the config’s own remote MCP servers use become the allowed_hosts allowlist, and everything else is denied.
  • Local skills are inlined by content. A skill that exists only on this machine has no upstream to install from, so its text is embedded, and the generated code can reproduce the setup on another machine.

The command reports every lossy step: a site it could not map (Cursor has no fountain runtime value), a model it had to default because the local config pinned none, and any site where a credential was redacted.

When the current project’s chant.config.ts declares the target lexicon by path, it is loaded from that module. The config is read without running it, and a note is printed when its lexicons cannot be read that way. See Commands that read chant.config.ts without running it.

A lexicon opts into being an --agents target by implementing agentConfigImporter(). See lexicon authoring.

The table below measures how often an import followed by a build gives back the template you started with, using 20 CloudFormation templates from the awslabs/aws-cloudformation-templates repository and 22 Kubernetes manifests from the Kubernetes documentation examples. Each was run through chant import and then chant build, and the original and the rebuilt document were compared after canonical ordering. The run was on 2026-10-08 with chant 0.109.0, and the results follow.

TargetInputsByte-identicalDifferImport or build failed
CloudFormation209101
Kubernetes222200

Canonical ordering means each file is parsed, every mapping has its keys sorted, and the document is printed as JSON with two-space indent. Arrays keep their order. CloudFormation short-form tags such as !Sub and !GetAtt are rewritten to their long form first. A Kubernetes file with several documents has them sorted by kind, namespace and name. Comments, quoting and indentation disappear when the file is parsed, so they are outside the measure. The build ran with telemetry attribution off, since that stamps OTEL_* environment variables onto workloads that the original never had.

Some inputs contain what a post-synth check reports as an error. With those checks on, the build stops before there is anything to compare, so the measurement turns them off. A normal build still reports them as errors.

CheckWhat the input contains
WAW049EC2 ingress open to 0.0.0.0/0 on port 80
WAW021, WAW039an RDS instance without encryption or backups
WAW042an S3 bucket without a TLS-only bucket policy
WK8005a literal value in a sensitive environment variable

All 22 manifests come back identical.

Nine of the 20 templates come back identical. apigateway_lambda_integration.yaml does not import, because it uses !Rain::Embed, a directive for the Rain preprocessor. It is not CloudFormation, and import does not read it. Run the template through rain pkg first.

The other ten build and differ from the original in these ways:

DifferenceTemplates
Fn::GetAZs: null comes back as Fn::GetAZs: "", which means the same region3
AWSTemplateFormatVersion is added when the original has none3
An empty section (Mappings: {}, Conditions: {}) or empty Properties: {} is dropped3
A DependsOn list of one item comes back as a string1

The inputs, their upstream URLs and licenses, and the latest results are in test/import-roundtrip/. npx tsx scripts/import-roundtrip-bytes.ts re-runs every input through the CLI and rewrites results.json and results.md there, with the five checks above turned off. It takes about a minute and needs the lexicons built.

Terminal window
# Import a template file
chant import template.json
# Import to a specific directory
chant import template.json --output src/
# Import Kubernetes manifests (one or many documents per file)
chant import manifests.yaml --output src/
# Outside a project: every installed lexicon is tried
chant import deployment.yaml
# Skip detection and name the lexicon
chant import manifests.yaml --lexicon k8s --output src/
# Pass an option to the lexicon's parser
chant import dashboard.json --lexicon grafana --parser-option acceptLossyV1
# Import live infrastructure from an environment
chant import --from prod
# Adopt a single orphaned resource back into source
chant import --from prod --name my-bucket --output src/
# Keep server-defaulted fields
chant import --from prod --verbatim
# Render an overlay and import what it produces
chant import --kustomize ./k8s/overlays/prod --output src/
CodeMeaning
0Import completed successfully
1Error (unrecognized template format, parse error, or write failure)

chant