prompts/skills/clojure-aero/SKILL.md
Aero is an EDN configuration library with reader tag extensions for profiles, environment variables, and references. Use when working with configuration files, environment-specific settings, or when you need explicit, intentful config management in Clojure.
npx skillsauth add ramblurr/nix-devenv clojure-aeroInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
A small library for explicit, intentful configuration using EDN with powerful reader tag extensions.
deps.edn:
aero/aero {:mvn/version "1.1.6"}
Leiningen:
[aero "1.1.6"]
See https://clojars.org/aero for the latest version.
(require '[aero.core :refer [read-config]])
;; Create config.edn:
;; {:greeting "World!"
;; :port #profile {:default 8000
;; :dev 8001
;; :prod 80}}
;; Read from classpath (recommended)
(read-config (clojure.java.io/resource "config.edn"))
;; => {:greeting "World!", :port 8000}
;; Read with profile
(read-config (clojure.java.io/resource "config.edn") {:profile :dev})
;; => {:greeting "World!", :port 8001}
Aero reads EDN configuration files with special reader tags that allow:
Always use io/resource to read from classpath - works in both REPL and JAR files. Direct file paths like (read-config "config.edn") fail in JARs.
{:webserver
{:port #profile {:default 8000
:dev 8001
:test 8002
:prod 80}}}
;; Usage:
(read-config "config.edn" {:profile :dev})
;; => {:webserver {:port 8001}}
{:database-uri #env DATABASE_URI}
;; Reads from (System/getenv "DATABASE_URI")
{:database #envf ["protocol://%s:%s" DATABASE_HOST DATABASE_NAME]}
;; Builds string from multiple env vars
{:port #or [#env PORT 8080]
:debug #boolean #or [#env DEBUG "true"]}
;; First available value wins, uses 8080 if PORT not set
{:db-connection "datomic:dynamo://dynamodb"
:webserver {:db #ref [:db-connection]}
:analytics {:db #ref [:db-connection]}}
;; Both :webserver and :analytics get same db-connection value
;; References use get-in vector paths
{:webserver #include "webserver.edn"
:analytics #include "analytics.edn"}
By default resolves relative to parent config. Use custom resolver:
(require '[aero.core :refer [resource-resolver root-resolver]])
;; Always resolve from classpath
(read-config "config.edn" {:resolver resource-resolver})
;; Or provide a map
(read-config "config.edn"
{:resolver {"webserver.edn" "resources/webserver/config.edn"}})
{:url #join ["jdbc:postgresql://psq-prod/prod?user="
#env PROD_USER
"&password="
#env PROD_PASSWD]}
{:config #merge [{:foo :bar} {:foo :baz :qux 123}]}
;; => {:config {:foo :baz :qux 123}}
{:port #long #or [#env PORT "8080"] ; Parse string to Long
:factor #double #env FACTOR ; Parse to Double
:mode #keyword #env MODE ; Parse to keyword
:debug #boolean #or [#env DEBUG "true"]} ; Parse to boolean
{:webserver
{:port #hostname {"stone" 8080
#{"emerald" "diamond"} 8081
:default 8082}}}
Like #hostname but switches on the current user.
Don't put secrets in version control or env vars. Use private files:
{:secrets #include #join [#env HOME "/.secrets.edn"]
:aws-secret-access-key
#profile {:test #ref [:secrets :aws-test-key]
:prod #ref [:secrets :aws-prod-key]}}
(ns myproj.config
(:require [aero.core :as aero]
[clojure.java.io :as io]))
(defn config [profile]
(aero/read-config (io/resource "config.edn") {:profile profile}))
(defn webserver-port [config]
(get-in config [:webserver :port]))
;; Usage in app:
(let [cfg (config :prod)]
(start-server :port (webserver-port cfg)))
This insulates your code from config structure changes.
{:features
{:new-ui #profile {:default false
:dev true
:staging true
:prod false}}}
Pass config to components without boilerplate:
(defn configure [system profile]
(let [config (aero/read-config (io/resource "config.edn")
{:profile profile})]
(merge-with merge system config)))
(defn new-system [profile]
(-> (new-system-map)
(configure profile)
(system-using (new-dependency-map))))
Extend the reader multimethod for custom tags:
(require '[aero.core :refer [reader]])
(defmethod reader 'mytag
[{:keys [profile] :as opts} tag value]
(if (= value :favorite)
:chocolate
:vanilla))
;; In config.edn:
;; {:flavor #mytag :favorite}
File paths vs resources: Use (io/resource "config.edn") not "config.edn" to avoid JAR deployment failures
Environment variables for secrets: Don't use #env for passwords - they leak via ps and monitoring. Use #include with private files instead
Single config file: Keep one config file when possible - easier to manage and less duplication
#or evaluation order: Tags evaluate left to right, first non-nil wins
References are recursive: #ref works across #include boundaries
Profile is just a key: Can be any keyword - :dev, :prod, :staging, :local, etc.
For custom conditional constructs, use the alpha API:
(ns myns
(:require [aero.alpha.core :as aero.alpha]))
(defmethod aero.alpha/eval-tagged-literal 'myprofile
[tagged-literal opts env ks]
(aero.alpha/expand-case (:profile opts) tagged-literal opts env ks))
See README for #or implementation example.
testing
Use this OCP when executing or preparing to execute commands that change a live or important system, service reloads/restarts, package changes, deployments, migrations, firewall/network/access changes, credential rotation, NixOS switch/test/boot/deploy, or incident mitigation. It guides safe operations with a persisted ledger for scope, preflight, baseline, rollback, validation, and evidence.
development
Create new agent skills with proper structure, progressive disclosure, and bundled resources. Use when user wants to create, write, or build a new skill.
documentation
Naming conventions for workflow documents in prompts/. Use when creating plans, PRDs, research reports, idea capture or other workflow documents. Triggers on (1) creating new planning documents, (2) naming PRDs or research reports, (3) questions about document organization in prompts/.
testing
Grilling session that challenges your plan against the existing domain model, sharpens terminology, and updates documentation (CONTEXT.md, ADRs) inline as decisions crystallise. Use when user wants to stress-test a plan against their project's language and documented decisions.