Commands¶
Beyond linting, flint generates GitOps YAML from real artifacts, repairs and
wires path: references, and scaffolds new files. Run flint <command> --help
for full flags, or flint help-ai --sop <paths|software> for step-by-step
workflows.
The local dry-run¶
flint dry-run # lint the whole repo; verdict: would gitops pass?
flint dry-run --strict # also gate on advisory warnings (zero-tolerance)
flint dry-run --json # machine-readable verdict (for CI)
flint dry-run --exclude '**/tools-scripts/fleet-templates/**'
flint dry-run is the local, server-free equivalent of fleetctl gitops
--dry-run — no Fleet server, no fleetctl. It runs the whole-repo lint and
prints a single verdict:
✓ Local dry-run: PASS — 101 file(s), 0 blocking (7 advisory warning(s))
# or
✗ Local dry-run: FAIL — 2 issue(s) would block `fleetctl gitops`:
…/corp-fonts.yml:2 [software-source] software package has hash_sha256 but no url installer source
Errors are blocking (exit code 2 — drop it straight into CI); warnings are
advisory unless --strict. Run it before every push (see Wire it in below).
Linting (detail)¶
flint dry-run is a whole-repo wrapper over flint check. Use check directly
for fixes, single files, and full diagnostics:
flint check . # full diagnostics for a repo (or a single file)
flint check . --fix # auto-apply Safe fixes (renames, typos)
flint check . --fix --unsafe-fixes
flint check . --format json # structured output for CI
flint check . --exclude '**/tools-scripts/fleet-templates/**' # skip a folder
flint list-rules # show enforced rules
See Getting started and Rules.
Wire it in¶
Make the local dry-run unavoidable so failures never reach the pipeline:
# CI — run BEFORE the server dry-run (e.g. in .github/.../gitops.sh):
flint dry-run . || exit 1 # local gate, no server needed
fleetctl gitops --dry-run # server dry-run only if the local one passes
Excluding files/folders¶
Skip template scaffolds or vendored YAML two ways:
- CLI:
--exclude '<glob>'(repeatable), e.g.flint check . --exclude '**/tools-scripts/fleet-templates/**'. .fleetlint.toml(persistent, applies to every run):
CLI --exclude globs are merged with files.exclude. Excludes apply to both
the per-file rules and the cross-file graph pass.
Catch dry-run failures locally (no server)¶
Many fleetctl gitops --dry-run rejections are statically detectable, so
flint check . catches them before the pipeline — no Fleet server or
fleetctl required. Beyond the per-file rules, a cross-file graph pass runs
on flint check <dir> (it needs the whole repo to resolve references):
label-reference— alabels_include_any/allorlabels_exclude_anyvalue that names no label defined in the repo (and is not a Fleet built-in likemacOSorAll Hosts). Catches typos and forgotten definitions.install-software-hash— a policyinstall_software.hash_sha256with no matching software package anywhere in the repo.install-software-team— a fleet/team whose policy auto-installs a package that that team doesn't include in itssoftwarelist (resolvingpackage_path/hash_sha256/fleet_maintained_app_slug/app_store_id, and policies pulled in by apaths:glob). The install-on-fail can't run for that team, and gitops reportspackage not found with hash. Coversfleets/andteams/.app-store-vpp—software.app_store_appsdeclared but novolume_purchasing_program(VPP) configured inorg_settings. App Store apps install via Apple's VPP; without itfleetctl gitopscan't apply them. (The VPP token /locationmatch / app availability are server-side — only the missing-config precondition is checked.)install-software-id— a policy whose query checks a differentpackage_idthan the package itsinstall_softwareinstalls (compared against the identifier in the referenced software file's# <id> (...)header comment). Such a policy can install the package yet never pass, so Fleet reinstalls until the 3-attempt cap then stops. Onlypackage_receipts.package_idqueries are compared (anapps.bundle_identifierquery is a different namespace and is skipped).
Troubleshooting: policy installs software but never passes / never runs¶
A valid config can still misbehave at runtime. The usual causes, none of which are dry-run errors:
package_idmismatch — the policy query checks one id but the package registers another, so the policy never goes green after install. Caught byinstall-software-id. Fix the query to match the id the package actually installs (pkgutil --pkg-info <id>).- Fleet's 3-attempt cap — policy-automation installs are tried up to 3 times; the count resets only when the host passes. After a mismatch (or an earlier failure) exhausts the attempts, Fleet stops trying. Re-applying gitops does not reset it — deselect/reselect Install software in Manage automations to clear the count and retrigger.
- Package not uploaded / not in the team — the install action needs a
package with the referenced hash uploaded to the target server and present in
the team's
softwarelist. Caught byinstall-software-team/software-source. (Install-on-policy-failure is Fleet Premium; installs time out after 1 hour; the software's label targeting must include the host.) software-url— asoftware.packages[].url(or a standalone software file'surl:) that is malformed, plus the unfilledREPLACE-MEplaceholder thatflint gen software --standaloneemits.software-source— a software package with ahash_sha256but nourl. Fleet can install it only if a package with that hash is already cached on the server; otherwise gitops fails withpackage not found with hash. This is what happens whenflint gen software's minimal block (- hash_sha256: …) is used as a software file instead offlint gen software --standalone(which scaffolds aurl:).
Both cross-file checks are warnings: GitOps legitimately allows referencing
a server-side label or a package already cached in Fleet by hash, so an
unresolved reference is a likely mistake, not a certain one. They run only on a
directory (not a single file) and are configurable by code in .fleetlint.toml.
Path references¶
flint understands the repo's path: / paths: dependency graph — both
directions.
Fix broken references¶
After a folder reorg, references to moved files break silently. flint paths
finds them and, when a file's new location is unambiguous, fixes them.
flint paths . # report broken path: refs (before → after)
flint paths . --fix # rewrite every unambiguous reference in place
It also catches a reference to an empty file (path-empty): a path:/
package_path:/run_script.path/install_script.path/… that resolves but has
no usable content — blank, whitespace-only, or (for YAML) comment-only (e.g. a
software file with just a # … version … header and no hash_sha256:, or an
empty .sh). It resolves on disk but fleetctl gitops rejects it. Covers every
path-bearing key (software, policies, scripts, profiles); the software file
itself is also flagged when linted directly (software-source).
This also catches case-only mismatches (path-case): a reference like
../lib/Foo.yml when the file on disk is lib/foo.yml resolves fine on
case-insensitive macOS but fails on case-sensitive CI (fleetctl gitops on
Linux) — the classic "passes locally, breaks the pipeline" trap. flint compares
each path component against the real on-disk name and suggests the exact casing
as a Safe fix. Applies to path: and install_software.package_path.
In the editor, broken references get a quick-fix lightbulb (one option per
candidate) plus a “Fix all N references to
Wire unwired artifacts¶
The inverse: files that exist on disk but no fleet/team config references.
flint paths . --unwired # report orphans, grouped + copy-paste blocks
flint paths . --unwired --interactive # walk each group, wire into chosen fleets
Interactive wiring asks per fleet ([y]es / [a]ll-remaining / [n]o / [s]kip /
[q]uit), then per group offers a glob (one rule for the directory) or
per-file entries that can carry labels_include_any / labels_exclude_any
(combinable: broad include + targeted exclude).
| Flag | Effect |
|---|---|
--label-stubs[=blank\|comment] |
on blank label answers, emit the empty key (blank, default) or a commented stub (comment) |
--only <glob> |
limit target fleets, e.g. --only 'fleets/acfg-*.yml' |
Each artifact is routed to the right section using current Fleet keys
(controls.apple_settings.configuration_profiles, controls.scripts,
controls.setup_experience.apple_setup_assistant, software.packages, …), with
paths written relative to where your fleet files actually live.
Generate YAML (flint gen)¶
One verb for flint's generator face: flint gen <kind> [--from <source>].
The --from source's extension picks the generator; omitting --from emits a
blank template where one exists.
Software stanzas¶
Read an installer and emit the software.packages block — no hand-transcription
of identifiers, versions, or hashes.
flint gen software --from app.pkg # comment header + hash_sha256
flint gen software --from app.pkg --full # full stanza: url placeholder, self_service, stubs
flint gen software --from app.pkg --full --setup-experience # add setup_experience: true (OOBE)
flint gen software --from app.pkg --wire # generate + interactively insert into a fleet
flint gen software --from app.pkg --standalone # write a standalone <name>.package.yml next to the .pkg
flint gen software --from app.pkg --standalone --full -o ./software # full scaffolding, random name in ./software
flint gen software --from app.pkg --standalone -o app.yml # exact filename (refuses to overwrite)
flint gen software --from installer.deb # other formats (see below)
flint gen software --from ./packages/ # batch a directory of .pkg files
--standalone writes a complete standalone software file — the top-level
mapping form a fleet/team file references via software.packages: [- path: …]
— rather than the inline list-item block. The name is randomized
(<name>.package.yml, name derived from the installer) so running it repeatedly never overwrites an earlier
file; pass -o <dir> to choose the directory, or -o <name>.yml for an exact
filename. hash_sha256 (the value flint gen software computes) leads the
file; url is a fill-in placeholder. Honors --full and --setup-experience.
Non-.pkg sources cover the other Fleet custom-package formats, extracting
metadata where the format and tooling allow (the SHA-256 is always computed):
| Format | Source |
|---|---|
.pkg |
xar (identifier + version) |
.deb |
ar + tar (Package / Version) |
.ipa |
unzip + plutil (CFBundleIdentifier / CFBundleShortVersionString) |
.msi |
msiinfo (msitools), if installed |
.rpm |
rpm, if installed |
.exe, .tar.gz |
hash + placeholders |
.dmgis not a Fleet custom-package format — repackage the.appinside as a.pkgfirst.
Keep a package current¶
On a new release of an already-referenced package:
Matches the stanza by identifier and refreshes hash_sha256, version, and the
url filename — leaving comments, labels, and self_service intact.
Install-state policies & default scripts¶
flint gen policy --from app.pkg # "installed & up to date" via package_receipts
flint gen policy --from app.pkg --apps # via the apps table (bundle_identifier)
flint gen policy --from app.pkg --enforce # + install_software automation (auto-install on fail)
flint gen policy --from app.pkg --outdated-only # fail only when an outdated copy is installed
flint gen policy --from app.pkg --file '/Applications/YourApp.app/Contents/Info.plist' # file-exists proxy
flint gen scripts --from app.pkg -o ./scripts # Fleet's default install.sh + uninstall.sh
Three verification strategies:
- default —
package_receiptsbypackage_id(+ semantic version). --apps— theappstable bybundle_identifier/bundle_short_version(for packages that install a.app).--file <PATH>— thefiletable:SELECT 1 FROM file WHERE path = '<PATH>'. An existence proxy (no version) for packages that don't register a clean receipt or app bundle — fonts, configs, dropped files. Combine with--enforceto auto-install on a missing file.
The version query uses EXISTS (…) AND NOT EXISTS (… version_compare(…) < target)
on osquery's semantic version_compare, not a single … >= target: a
package can have multiple receipts, and the single-comparison form passes if any
copy is current even when an older one lingers. The EXISTS/NOT EXISTS form
passes only when the package is installed and no copy is older than the
target — fails on missing or outdated.
--outdated-onlydrops theEXISTSgate: the policy passes when the package is absent or current and fails only on an outdated copy — patch existing installs without forcing a fresh install on hosts that don't have it.
By default it is an audit policy — it reports drift but doesn't remediate.
Add --enforce to append an install_software automation that references the
package by the hash_sha256 flint gen software computed, so Fleet
auto-installs it on hosts that fail the policy. The package must also be listed
under software in the same team (by that hash). The scripts are Fleet's
verbatim defaults ($PACKAGE_ID is substituted by the Fleet server at upload).
Configuration profiles¶
flint gen profile --from wifi.mobileconfig # → configuration_profiles entry
flint gen profile --from passcode.json # DDM declaration (Type + Identifier)
flint gen profile --from firewall.xml # Windows CSP (LocURI context)
flint gen profile --from enroll.dep.json # → setup_experience.apple_setup_assistant
flint gen profile --from wifi.mobileconfig --full # add labels_* stubs
flint gen profile --from wifi.mobileconfig --wire # insert into a fleet (path per-fleet-relative)
The header includes the display name, identifier, scope, and the nested
PayloadType (the meaningful one, not the Configuration wrapper).
Optional: Apple-schema validation via contour
flint checks the Fleet side of a profile — is it referenced, is it wired
into a fleet, is its PayloadUUID unique — and never inspects the payload
itself. If contour is installed,
gen profile additionally validates the profile against Apple's schema
and prints anything it finds to stderr, so stdout stays
copy-pasteable and flint gen profile … > entry.yml is unaffected.
Entirely optional: with contour absent the output is byte-identical and nothing is printed. Absence of findings therefore means "not checked", not "no problems".
Mitigate a duplicate-payload-uuid finding by minting a fresh UUID:
Queries & blank templates¶
flint gen query --from check.sql # query stanza; platform inferred from osquery tables
flint gen policy --from check.sql # policy stanza around that query
flint gen profile # starter .mobileconfig (fresh PayloadUUID)
flint gen fleet|policy|query|label # blank templates
gen query infers platform: from the intersection of the platforms of the
osquery tables the SQL references; unknown tables leave a commented placeholder.
Migrating from v0.1.x¶
The legacy generator commands keep working until v0.3.0 (hidden from
--help, warning on stderr):
| v0.1.x | v0.2.0 |
|---|---|
flint pkg X.pkg [--full\|--wire\|-o F\|--setup-experience] |
flint gen software --from X.pkg [same] |
flint app X.deb (any format) |
flint gen software --from X.deb |
flint pkg X.pkg --yml |
flint gen software --from X.pkg --standalone |
flint pkg X.pkg --update F |
flint gen software --from X.pkg --update F |
flint pkg X.pkg --policy [--apps\|--enforce\|--outdated-only\|--file P] |
flint gen policy --from X.pkg [same] |
flint pkg X.pkg --scripts DIR |
flint gen scripts --from X.pkg -o DIR |
flint query X.sql |
flint gen query --from X.sql |
flint query X.sql --policy |
flint gen policy --from X.sql |
flint profile X [--full\|--wire\|--regen-uuid\|-o] |
flint gen profile --from X [same] |
flint new profile\|fleet\|policy\|query\|label |
flint gen <kind> |
flint help-agents --install-skill |
flint setup-agent |
Fleet Maintained Apps (flint fma)¶
Slug lookup for fleet_maintained_apps: entries and patch policies — the
same registry that powers editor completions and the fma-slug lint rule.
flint fma search slack # ready-to-paste slugs (slack/darwin, slack/windows)
flint fma show raycast # platforms, latest version, installer URL
flint fma latest --days 7 # recent version bumps from fmalibrary.com
flint fma refresh # update the local registry cache from the feed
refresh writes ~/.cache/flint/fma-cache.toml; lint, completions, and
search all pick it up immediately. Everything accepts --json.
Fleet instance views (flint fleet)¶
Read-only — these commands can never modify your instance. Connection comes
from .fleetlint.toml [fleet] (or ~/.config/flint/config.toml), with
FLEET_URL/FLEET_API_TOKEN env or ./.env fallback; op:// secret
references resolve via the 1Password CLI.
flint fleet status # server version — connection sanity check
flint fleet software --team 5 --available # installable titles on the instance
flint fleet fma # FMA catalog YOUR Fleet version offers
flint fleet labels # labels defined on the instance
flint fleet teams # teams (fleets) with host counts
Agents & editors¶
flint setup-agent # install Claude Code skills
flint help-ai # command reference for agents
flint help-ai --sop paths # broken-path + unwired workflows
flint help-ai --sop software # artifact-generation workflows
flint lsp # language server (run by editor extensions)
See Editors for editor setup.