Claude Code Plugins

Community-maintained marketplace

Feedback
0
0

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.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name clojure-aero
description 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.

Aero

A small library for explicit, intentful configuration using EDN with powerful reader tag extensions.

Setup

deps.edn:

aero/aero {:mvn/version "1.1.6"}

Leiningen:

[aero "1.1.6"]

See https://clojars.org/aero for the latest version.

Quick Start

(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}

Core Concepts

Aero reads EDN configuration files with special reader tags that allow:

  • Profile-based configuration (dev/test/prod)
  • Environment variable injection
  • References to other config values
  • File inclusion for modular configs
  • Type coercion (string to long/double/keyword/boolean)
  • Conditional logic based on hostname, user, etc.

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.

Reader Tags

#profile - Environment-specific values

{:webserver
  {:port #profile {:default 8000
                   :dev 8001
                   :test 8002
                   :prod 80}}}

;; Usage:
(read-config "config.edn" {:profile :dev})
;; => {:webserver {:port 8001}}

#env - Environment variables

{:database-uri #env DATABASE_URI}

;; Reads from (System/getenv "DATABASE_URI")

#envf - Format with environment variables

{:database #envf ["protocol://%s:%s" DATABASE_HOST DATABASE_NAME]}

;; Builds string from multiple env vars

#or - Provide defaults

{:port #or [#env PORT 8080]
 :debug #boolean #or [#env DEBUG "true"]}

;; First available value wins, uses 8080 if PORT not set

#ref - Reference other config values

{: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

#include - Modular configs

{: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"}})

#join - String concatenation

{:url #join ["jdbc:postgresql://psq-prod/prod?user="
             #env PROD_USER
             "&password="
             #env PROD_PASSWD]}

#merge - Merge maps

{:config #merge [{:foo :bar} {:foo :baz :qux 123}]}
;; => {:config {:foo :baz :qux 123}}

Type coercion tags

{: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

#hostname - Host-specific config

{:webserver
  {:port #hostname {"stone" 8080
                    #{"emerald" "diamond"} 8081
                    :default 8082}}}

#user - User-specific config

Like #hostname but switches on the current user.

Common Patterns

Hide passwords in local files

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]}}

Wrap config access in functions

(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.

Feature toggles

{:features
  {:new-ui #profile {:default false
                     :dev true
                     :staging true
                     :prod false}}}

Component integration

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))))

Custom Reader Tags

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}

Gotchas

  1. File paths vs resources: Use (io/resource "config.edn") not "config.edn" to avoid JAR deployment failures

  2. Environment variables for secrets: Don't use #env for passwords - they leak via ps and monitoring. Use #include with private files instead

  3. Single config file: Keep one config file when possible - easier to manage and less duplication

  4. #or evaluation order: Tags evaluate left to right, first non-nil wins

  5. References are recursive: #ref works across #include boundaries

  6. Profile is just a key: Can be any keyword - :dev, :prod, :staging, :local, etc.

Advanced: Macro Tag Literals (Alpha)

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.

References