Skip to content

Latest commit

 

History

History
643 lines (495 loc) · 28.9 KB

cli-reference.md

File metadata and controls

643 lines (495 loc) · 28.9 KB

CLI reference

Complete flag reference for every Flecto command. For task-oriented guides, start with the README.

Global:

flecto --version
flecto --help
flecto help <command>

flecto watch [files...]

Watch config files or globs and print semantic changes as they happen. Also hosts the snapshot and diff modes.

flecto watch config/prod.yaml
flecto watch "config/**/*.yaml" ".env"

If you omit files, Flecto uses the files / include patterns from .flectorc.

Flag Default Description
-p, --profile <name> — Use a profile from .flectorc (else FLECTO_PROFILE)
-m, --mode <mode> compact Output mode: compact or verbose
-i, --interval <ms> 100 Polling interval, used when --polling is on
--polling off Force polling instead of native filesystem events
-c, --command <cmd> — Shell command to run on every change
-w, --webhook <url> — POST the change envelope to this URL
--webhook-header <header> — Extra webhook header, repeatable
--webhook-timeout <ms> 5000 Webhook request timeout
--webhook-retries <n> 2 Webhook retry attempts
--webhook-format <service> flecto Payload shape: flecto, slack, discord, teams, or auto (detect from the URL)
--delivery-mode <mode> best-effort best-effort or at-least-once
--on-alert-failure <mode> warn warn, exit, or retry
--ignore <keys> — Comma-separated key paths to ignore
--policies <ids> default Comma-separated policy pack ids
--plugins <paths> — Comma-separated local ESM plugin paths
--array-id-key <key> auto Diff arrays by this identity key
--no-array-id — Diff arrays by index instead of identity
--array-ignore-order off Treat array order as insignificant
--mask-secrets off Mask secret-like values in terminal output
--mask-secrets-webhooks off Also mask secrets in webhook payloads
--snapshot — Save current state as a baseline instead of watching
--diff — Diff against the saved baseline and exit
--allow-empty off Let --snapshot succeed when nothing was written
--snapshot-store <id> local Where snapshots live: local or shared
--snapshot-dir <path> per store Override the store directory
--snapshot-mask <mode> per store How the store records secret-like values: hash or none
--snapshot-retention <n> per store Snapshots kept per file; 0 keeps every one

See Snapshot stores for what the two stores are for.

Exit codes: watch mode runs until interrupted (0 on clean shutdown). With --diff: 0 when the file matches the baseline, 1 when it differs, and 1 with an error when no target had a saved snapshot — exiting 0 there would report "no drift" from a comparison that never happened. Targets that individually lack a snapshot are warned about and counted, and the rest still diff.

See configuration for --ignore pattern syntax and array identity behavior, and webhooks for delivery details.


flecto ci [files...]

Run a one-shot semantic diff against a baseline and exit with a status code CI can gate on.

flecto ci "config/**/*.yaml" --snapshot-ref HEAD~1 --fail-on "policy,error"
Flag Default Description
-p, --profile <name> — Use a profile from .flectorc (else FLECTO_PROFILE)
--snapshot-ref <ref> local snapshot Baseline git revision. A value that is unambiguously a path (absolute, ./, ../) is read as a snapshot file instead
--snapshot-file <path> — Baseline snapshot file, never consulted as a git revision
--format <type> json json, ndjson, sarif, github-annotations, or pr-comment
--pr-comment-post off With --format pr-comment, upsert the sticky comment on the PR
--pr-provider <name> detect Force the delivery target: github, gitlab, or bitbucket
--fail-on <rules> changed,policy,error Comma-separated fail triggers
--baseline <file> — Gate only on findings not already recorded in this file
--update-baseline off Rewrite the --baseline file from the current findings
--ignore <keys> — Comma-separated key paths to ignore
--policies <ids> default Comma-separated policy pack ids
--plugins <paths> — Comma-separated local ESM plugin paths
--array-id-key <key> auto Diff arrays by this identity key
--no-array-id — Diff arrays by index instead of identity
--array-ignore-order off Treat array order as insignificant
--mask-secrets off Mask secret-like values in CI output
--show-suppressed off List inline-suppressed findings instead of only counting them
--allow-empty off Succeed when no files were diffed
--explain off Add model-generated narration of the masked diff; advisory, never affects the exit code. See explain.md
--explain-dry-run off Print the narration request --explain would send (to stderr), and send nothing

Inline suppressions (# flecto-ignore-next-line <rule> — <reason>) accept a single deliberate finding in place; a reason is mandatory. See ci.md.

Fail triggers: changed, added, removed, policy, error, warn.

Exit codes: 0 when nothing matched a fail trigger, 1 when something did or when the run errored.

Two things fail closed by design: an unresolved --snapshot-ref is an error rather than a silently empty baseline, and a run where every target is missing or unsupported exits non-zero unless you pass --allow-empty.

With no --snapshot-ref, the baseline comes from the snapshot store — by default the local one in .flecto-snapshots/, which is not committed and does not survive an ephemeral runner. A file with no saved snapshot is an error naming the store it looked in and the ways out — save one with flecto watch <file> --snapshot, commit a --snapshot-store shared store, or diff against a committed revision with --snapshot-ref <git-ref> — rather than a diff against nothing.

--snapshot-store <id> and --snapshot-dir <path> select which store ci reads. See Snapshot stores.

--format pr-comment prints a markdown risk summary to stdout. It posts that summary as a single sticky pull request comment only when --pr-comment-post is passed and GITHUB_TOKEN, GITHUB_REPOSITORY, and a PR number (from GITHUB_REF or GITHUB_EVENT_PATH) are all present — otherwise it warns and prints. A delivery failure never changes the exit code.

Full CI guide, including the GitHub Action: ci.md.


flecto explain [files...]

Ask a model you configure to narrate the likely blast radius of the semantic diff. Opt-in, bring your own key, and advisory only.

FLECTO_EXPLAIN_PROVIDER=anthropic FLECTO_EXPLAIN_API_KEY=sk-ant-... \
  flecto explain config/prod.yaml --snapshot-ref origin/main
Flag Default Description
-p, --profile <name> — Use a profile from .flectorc for diff and policy options
--snapshot-ref <ref> snapshot store Baseline git revision (a path-shaped value is read as a snapshot file)
--snapshot-file <path> — Baseline snapshot file, never consulted as a git revision
--snapshot-store <id> / --snapshot-dir <path> local Which snapshot store to read when there is no --snapshot-ref
--provider <id> FLECTO_EXPLAIN_PROVIDER anthropic or openai (any OpenAI-compatible server)
--model <name> FLECTO_EXPLAIN_MODEL Model id; anthropic defaults to claude-opus-5
--max-tokens <n> 16000 Output token cap
--format <type> human human or json
--dry-run off Print the exact request (key replaced by its source) and send nothing
--no-cache — Neither read nor write the narration cache
--ignore, --policies, --plugins, --array-id-key, --no-array-id, --array-ignore-order — As for ci

Only the masked semantic diff is sent (changed paths, before and after values, and findings), masked whether or not --mask-secrets is passed. The output is labeled as model-generated. explain* options declared in .flectorc are refused, and the provider, endpoint, and key come only from the command line and the environment.

Exit codes: 0 whether or not narration succeeded. A provider failure is a warning, never a failed run. 1 for an invalid invocation: no provider configured, an unknown provider, or an unresolvable baseline.

Full guide, including what is sent, caching, and cost: explain.md.


flecto compare <fileA> <fileB>

Diff two different files against each other — the same differ and policy engine watch and ci use, pointed at environment skew instead of drift over time.

flecto compare config/prod.yaml config/staging.yaml
flecto compare config/prod.yaml config/prod.json --format json

fileA is the baseline. Changes are reported as what fileB does to it: + exists only in fileB, - exists only in fileA, ~ is a value that differs. Swap the arguments to flip the direction.

Both arguments are single file paths, not globs, and both are required — a missing one is an error rather than a skipped target. The two files do not have to share a format, since every supported format parses to a plain tree.

Flag Default Description
-p, --profile <name> — Use a profile from .flectorc (else FLECTO_PROFILE)
--format <type> human human, json, ndjson, or github-annotations
--fail-on <rules> changed,added,removed,policy,error Comma-separated fail triggers
--ignore <keys> — Comma-separated key paths to ignore
--policies <ids> default Comma-separated policy pack ids
--plugins <paths> — Comma-separated local ESM plugin paths
--array-id-key <key> auto Diff arrays by this identity key
--no-array-id — Diff arrays by index instead of identity
--array-ignore-order off Treat array order as insignificant
--mask-secrets off Mask secret-like values in output

Fail triggers: the same set ci uses — changed, added, removed, policy, error, warn. The default differs from ci: two environments that are meant to match should also match on added and removed keys, so all three change kinds gate by default.

Exit codes: 0 when the two files match under the active fail triggers, 1 when they differ, a policy trigger fires, or the run errored.

The non-human formats emit exactly what ci emits — an array of per-file results, each with a 2.0 envelope — plus a baseline field naming fileA. Envelopes carry source: "diff".


flecto plan <planFiles...>

Diff Terraform plan JSON (the output of terraform show -json) and run policies on it — the same differ, envelope, and exit-code model every other Flecto command uses, pointed at a plan instead of a config file.

terraform plan -out plan.tfplan
terraform show -json plan.tfplan > plan.json

flecto plan plan.json

Flecto never runs the terraform binary; it only reads the JSON you hand it.

Flag Default Description
-p, --profile <name> — Use a profile from .flectorc (else FLECTO_PROFILE)
--format <type> human human, json, ndjson, github-annotations, or pr-comment
--pr-comment-post off With --format pr-comment, upsert the comment on the PR (needs a token + merge request context)
--pr-provider <name> detect Force the delivery target: github, gitlab, or bitbucket
--fail-on <rules> error Comma-separated fail rules: changed, added, removed, policy, error, warn
--ignore <keys> — Comma-separated key paths to ignore, e.g. **.tags_all,**.#action
--policies <ids> terraform Comma-separated policy pack ids
--plugins <paths> — Comma-separated local ESM plugin paths
--mask-secrets off Also mask Flecto-detected secret-like values (Terraform-sensitive values are always redacted, regardless of this flag)

Fail triggers: the same set ci uses — changed, added, removed, policy, error, warn. The default is error alone, not changed: a plan is supposed to contain changes, so gating on their existence would fail every non-empty plan.

Exit codes: 0 when nothing matched a fail trigger, 1 when something did or the run errored.

A replace (destroy-and-recreate) is reported as a removed event, so --fail-on removed catches every replace with no policy pack loaded at all. Values Terraform marks sensitive are redacted to (sensitive value) unconditionally, before any formatter sees them.

Several plan files in one run each produce their own result entry, exactly like ci with multiple files.

Full guide, including the terraform policy pack and CI wiring: terraform.md.


flecto history [files...]

Summarize drift across saved snapshots. Omit files to show all saved history. Reads whichever snapshot store is selected — the local one in .flecto-snapshots/ by default.

flecto history config/prod.yaml --limit 10
Flag Default Description
-l, --limit <n> 10 Number of recent snapshots to show
-p, --profile <name> — Use a profile from .flectorc (else FLECTO_PROFILE)
--snapshot-store <id> local Store to read: local or shared
--snapshot-dir <path> per store Override the store directory
--ignore <keys> — Comma-separated key paths to ignore
--array-id-key <key> auto Diff arrays by this identity key
--array-ignore-order off Treat array order as insignificant

Change counts use the same ignore paths, array identity, and order settings as flecto watch --diff. This command is entirely local — it reads snapshot files and sends nothing anywhere. The header names the store it read and how many snapshots it found, so an empty answer is never mistaken for a clean one.

A snapshot with nothing before it reads as baseline, not 0 changes. The first snapshot of a file was never compared against anything, so a change count for it would be a result nobody computed. When every snapshot shown is a baseline, the command says so on stderr: no history is not no drift.


flecto report [files...]

Render the same snapshot history flecto history summarizes as a single self-contained HTML file you can share. Omit files to report on all saved history.

flecto report
flecto report config/prod.yaml --limit 20 --output drift.html
flecto report --profile prod --mask-secrets
Flag Default Description
-o, --output <path> flecto-report.html Where to write the report
-l, --limit <n> 10 Number of recent snapshots to include
-p, --profile <name> — Use a profile from .flectorc (else FLECTO_PROFILE)
--snapshot-store <id> local Store to read: local or shared
--snapshot-dir <path> per store Override the store directory
--ignore <keys> — Comma-separated key paths to ignore
--policies <ids> default Comma-separated policy pack ids
--plugins <paths> — Comma-separated local ESM plugin paths
--array-id-key <key> auto Diff arrays by this identity key
--no-array-id — Diff arrays by index instead of identity
--array-ignore-order off Treat array order as insignificant
--mask-secrets off Mask secret-like values in the report

The page holds a per-file timeline of snapshots — each with its UTC timestamp, the snapshot it is measured against, every semantic change, and the policy findings that change produced — plus a summary and all findings grouped by severity. Missing directories in --output are created.

The file is self-contained. Inline CSS, one small inline script for filtering and collapsing, and nothing else: no fonts, no images, no CDN scripts, no analytics, no network access when it is opened. It reads the same snapshot store flecto history reads, so nothing new is collected and nothing leaves your machine. It follows the viewer's light or dark theme and prints reasonably.

Every config value, path, and message is HTML-escaped, so a value containing markup renders as text. Values that reach the page are the only place a secret could leak from a report, so pass --mask-secrets (or set maskSecrets in a profile) whenever you plan to share one: masking uses the same key-name and value-pattern detection as everywhere else, and it also redacts policy messages that interpolate values.

The default store is local to the working directory. .flecto-snapshots/ is not committed and does not survive an ephemeral CI runner, so a pipeline that runs flecto report without first saving snapshots has nothing to report on — use --snapshot-store shared and commit it to report on a runner. A report whose snapshots are all first-of-their-file says so in a banner and in a Comparisons tile reading 0 — an empty report is no history, never an all-clear. Individual first snapshots are labelled baseline rather than 0 changes, for the same reason.

Exit codes: 0 when a report was written, 1 when no snapshots matched — the same message flecto history gives, and no file is written.


flecto policies add <name>

Install a policy pack from an npm package that follows the flecto-pack-* convention. The package must already be installed; this command does not reach the network.

npm install --save-dev flecto-pack-deployment-safety
flecto policies add deployment-safety
flecto policies add flecto-pack-deployment-safety   # same thing
flecto policies add deployment-safety --force       # after npm update
Flag Default Description
--force off Overwrite an existing local pack with the same id

<name> is either the pack id or the full package name; the two are normalized onto each other, and @scope/flecto-pack-<id> is supported. The pack is validated with the same validator used at evaluation time, then written to policies/<id>.json, where the normal resolution order finds it. Provenance is recorded in policies/.flecto-packs.json and shown by flecto policies list; resolution itself never consults that file.

Only the package's declarative pack file is read — no JavaScript from the package is imported or run, and JS in a pack package is ignored. Plugins, which do execute code, are never installed by this command.

Exit codes: 0 when the pack is written, 1 when the package is not installed, carries no valid pack, or would overwrite a local pack without --force.

See installing community packs and distributing a pack.


flecto policies list

List every bundled and local policy pack that resolves from the current directory, with its source path and rule count.

flecto policies list
flecto policies list --json
Flag Default Description
--json off Machine-readable output

Resolution order for a given pack id: policies/<id>.json, then policies/<id>.yaml, then policies/<id>.yml, then the built-in pack. A local pack with the same id overrides its built-in counterpart.

Packs installed with flecto policies add carry a package field naming the npm package they came from (- in the text output when a pack was written by hand).


flecto policies test <fixtureDir>

Assert that a policy pack or plugin produces the findings you expect, from a fixture directory.

flecto policies test examples/fixtures/policies
Flag Default Description
--config <name> flecto-policy-test.json Fixture config file name

Exit codes: 0 when the fixture's expectations hold, 1 otherwise.

A worked example lives in examples/fixtures/policies. See the policy pack guide for authoring.


flecto init

Write a starter .flectorc.json in the current directory, pre-selecting policy packs and file patterns from the stack signals it finds there.

flecto init
Detected Effect
docker-compose.yml / .yaml, compose.yml / .yaml compose pack + the compose file in files
package.json node-runtime pack + package.json in files
config/ config/**/*.{yaml,yml,json,toml,ini} in files
.env, .env.*, *.env .env, .env.*, *.env in files
*.tf Reported only — no terraform pack exists yet, and .tf is not parseable

The default pack is always enabled, and detected packs are added to the prod profile alongside strict-prod. With no signals, the generic starter config is written instead.

An existing .flectorc, .flectorc.json, .flectorc.yaml, or .flectorc.yml is never overwritten: init reports the file it found and leaves it alone.

Exit codes: 0 whether a config was written or an existing one was kept.

See configuration for the generated keys.


flecto mcp

Run Flecto as a read-only Model Context Protocol server over stdio, so an agent gets a small structured diff instead of reading a whole config file into its context.

flecto mcp

It exposes three read-only tools — flecto_diff, flecto_check, and flecto_explain — each running the same ci path a pull request triggers and returning the JSON envelope. Secrets are masked by default here (inverted from the CLI, because the consumer is a model context), plugins stay off, and tool arguments are path-contained. stdout carries JSON-RPC only; diagnostics go to stderr.

Exit codes: runs until stdin closes; setup or fatal errors exit 1.

See Flecto as an MCP server for client setup (Claude Code, Cursor) and the full security posture.


flecto lsp

Run a Language Server Protocol server over stdio. Open config files get Flecto's policy findings and semantic changes as diagnostics, anchored on the lines they're about.

flecto lsp --stdio
Flag Default Description
--stdio — Accepted and ignored; stdio is the only transport
-p, --profile <name> — Use a profile from .flectorc (else FLECTO_PROFILE)
--snapshot-ref <ref> HEAD Git ref open files are compared with
--snapshot-store <id> — Compare with a snapshot store (local/shared) instead of git
--snapshot-dir <path> store default Directory holding the snapshot store
--plugins <paths> — Comma-separated absolute plugin paths; plugins in .flectorc are never loaded
--changes <level> hint How semantic changes appear: hint, info, or none
--debounce <ms> 250 Quiet period after an edit before analyzing
--timeout <ms> 10000 Stop an analysis that runs longer than this

Findings use the same .flectorc, packs, inline suppressions, and --baseline file as ci, so the editor agrees with the gate. Analyses run in a worker thread: a newer edit cancels a running one, and one past --timeout becomes a single warning. stdout carries JSON-RPC only; diagnostics go to stderr.

Exit codes: 0 after an LSP shutdown then exit, 1 for an exit without shutdown or an invalid flag.

Editor setup and how diagnostics are positioned: editor.md.


flecto doctor

Check the local setup: which config file resolved, how many files its patterns match, and whether the Node.js runtime is supported.

flecto doctor

Exit codes: 0 when the environment is usable, 1 on an unsupported Node.js version or a broken config.


Snapshot stores

Snapshots are the baseline every drift feature measures against: watch --diff, ci without --snapshot-ref, history, and report all read one. Where they live is selectable (#141), and the two stores answer different questions.

local (default) shared
Directory .flecto-snapshots/ .flecto/snapshots/
Keyed by absolute path hash repo-relative path
Layout one file per snapshot one file per config file, history inside
Masking off hash — secret-like values stored as digests
Retention unlimited 20 snapshots per file
Committed no (gitignored) yes, that is the point

local is a scratch directory on your machine. It is keyed by the file's absolute path, so it means nothing on another checkout, and nothing about it changed in this release.

shared is meant to be committed. Because it is keyed by repo-relative path and serialized deterministically, the snapshot you save on a laptop is the baseline a CI runner reads on a checkout it has never seen before:

flecto watch "config/**/*.yaml" --snapshot --snapshot-store shared
git add .flecto/snapshots && git commit -m "chore: update config baseline"
# on the runner — no --snapshot-ref, no cache, no setup step
flecto ci "config/**/*.yaml" --snapshot-store shared

Set it once in .flectorc ("defaults": { "snapshotStore": "shared" }) so the editor of a config and the gate that blocks their pull request read the same history.

Committing snapshots means committing config

Snapshots carry config values, and git history is permanent. That is why the shared store masks by default: every value that trips Flecto's secret detection is stored as flecto:sha256:<digest> rather than in the clear.

Detection is the same on both counts --mask-secrets uses — the shape of a value (an opaque high-entropy string, a private key block, a URL with credentials in it) and the name of its key (password, token, secret, api_key, private_key, credential). The key-name half is the one that catches password: hunter2, which looks like nothing at all, and it matters more here than in the terminal: a log scrolls away, a commit does not. Keys are matched on the configuration path, so a Kubernetes resource named token-service does not turn every value inside it into a digest.

The digest is a change detector, not a vault. Rotating a credential changes the digest, so drift is still reported — the point of a store that silently missed a rotated key would be hard to defend — but a digest of a low-entropy value can be brute-forced, so a masked snapshot is not a safe place to put something that was never meant to be committed. --snapshot-mask none turns masking off and writes values verbatim; the CLI says so when it does.

Masking is recorded in each store file. Switching modes mid-history warns, because entries written in the other form will read as changed on the next diff.

Diffs against a masked store

When the baseline is masked, the live file is masked the same way before the two are compared, so an untouched secret produces no change and a rotated one shows as flecto:sha256:aaa… → flecto:sha256:bbb…. Neither value appears anywhere.

Retention

--snapshot-retention <n> caps how many snapshots a file keeps, oldest pruned first; 0 keeps every one. The shared store defaults to 20 because an append-forever directory inside a repository becomes its own problem. The local store defaults to unlimited, as it always has.

Serialization

Shared-store files sort object keys at every level and end with a newline, so reordering keys in a config produces no diff at all and a real change produces exactly the lines that changed. A snapshot commit is meant to be reviewable.


Environment variables

Variable Read by Description
FLECTO_PROFILE all commands Profile name, when --profile is not passed
FLECTO_ALLOW_SYMLINK_TARGETS all commands 1 allows a target — or a write destination — that leaves the project through a symlink
FLECTO_ALLOW_RC_WRITES ci, report 1 allows .flectorc to point --output / --baseline outside the project
FLECTO_ALLOW_RC_PLUGINS all commands 1 allows plugins declared in .flectorc
FLECTO_ALLOW_RC_ALERTS watch 1 allows .flectorc to declare command / webhook / webhookHeader
FLECTO_ALLOW_RC_BASELINE ci 1 allows .flectorc to declare snapshotRef, which chooses what every change is measured against
FLECTO_EXPLAIN_PROVIDER, _MODEL, _API_KEY, _API_URL explain, ci --explain Narration provider settings; see explain.md
FLECTO_EXPLAIN_MAX_TOKENS, _MAX_INPUT_TOKENS, _TIMEOUT_MS, _CACHE_DIR explain, ci --explain Narration cost, time, and cache bounds
FLECTO_EXPLAIN explain, ci --explain 0 disables narration on this runner

Flecto also sets variables for --command subprocesses — see webhooks and commands.