Adaptive Environment Manager (AEM) is a tool designed to simplify and automate the management of development environment dependencies such as Node.js, Java, and Android SDK components.
AEM is especially useful for developers working with frameworks such as React Native, where managing multiple versions of development tools across projects can become cumbersome and time-consuming.
By automating environment setup and version management, AEM enables developers to spend less time configuring development environments and more time building software.
Screen.Recording.2026-08-11.at.3.34.37.PM.mov
- Overview
- Features
- Supported Packages
- Installation
- Usage
- Project Configuration
- Android Configuration
- Setup Hooks
- Environment Setup
- Shell Environment Integration
- Providers, Cache, and Upgrades
- Troubleshooting
- Releases
- Contribution
- Motivation
- Future Plans
- Contact
Modern development environments often require multiple versions of Node.js, Java JDK, Android SDK platforms, Android NDK, Build Tools, and other development dependencies.
Different projects may require different versions of these tools. Manually installing, configuring, and switching between them can be error-prone and time-consuming.
AEM provides a centralized way to install, manage, and switch between development environment dependencies.
AEM can:
- Install and manage multiple Node.js versions.
- Download and manage Java JDK versions.
- Install and manage Android SDK components.
- Manage Android NDK versions.
- Manage Android Build Tools versions.
- Manage CMake versions.
- Configure project-specific development environments.
- Automatically install missing dependencies from
aem.json. - Switch active tool versions.
- Provide a consistent environment across macOS, Linux, and Windows.
Although React Native is one of the primary use cases, AEM itself is framework-agnostic.
AEM does not determine which versions a framework requires. Instead, the developer explicitly defines the required environment in the project's aem.json.
Install, list, switch, and manage multiple Node.js versions.
aem install node 20
aem use node 20.11.1
aem list nodeDownload and install Java JDK distributions and manage multiple JDK versions.
aem install java 17
aem use java 17.0.15
aem list javaAEM currently supports downloading JDKs through the Azul Zulu API.
AEM can automate Android development environment setup, including:
- Android SDK platforms
- Android Build Tools
- Android NDK
- CMake
- Android system images
- Android SDK command-line tools
- Android Platform Tools
Install an individual Android component by its raw sdkmanager package ID;
AEM handles the command-line tools, license acceptance, and installation:
aem install android "platforms;android-37.1"Quote package IDs containing ; so the shell passes them to AEM as one
argument. The semicolon is interpreted by shells as a command separator.
Projects can define their required environment in an aem.json file.
Running:
aem setupautomatically installs and activates the dependencies declared by the project.
Create a project-specific canonical aem.json:
aem initaem init opens a terminal menu that scans AEM-managed and PATH-available
Node.js and Java runtimes, plus configurable packages from the local Android
SDK. Choose one runtime version with the arrow keys, and use Space to toggle
Android platforms, Build Tools, NDKs, CMake versions, and system images. Android
components start unchecked, so select only what the project requires.
Use aem init --force only when you deliberately want to replace an existing
configuration.
AEM is designed to support:
- Windows
- macOS
- Linux
AEM is written in Go, providing a lightweight CLI with minimal runtime dependencies.
AEM uses a provider-based architecture so additional runtimes and development tools can be added in the future.
Manage multiple Node.js distributions and versions.
aem install node 20
aem list node
aem use node 20.11.1
aem available node
aem uninstall node 20.11.1Manage multiple JDK versions.
aem install java 17
aem list java
aem use java 17.0.15JDK downloads are currently provided through the Azul Zulu API.
AEM installs and reports these managed Android SDK components:
- SDK platforms
- Build Tools
- NDK
- CMake
- System images
Remove an individual Android component by its raw sdkmanager package ID.
The command-line tools remain installed because AEM uses them to manage other
components:
aem uninstall android "platforms;android-37.1"- Platform Tools
- Command-line Tools
- Internet connection for downloading SDKs and runtimes
curl -fsSL https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/refs/heads/main/scripts/install.sh | bashOn macOS and Linux, the installer downloads the matching released aem binary,
verifies it against the published checksum, installs it to
$AEM_HOME/bin/aem (default: ~/.aem/bin/aem), and updates the detected Bash
or Zsh profile. Open a new terminal after it completes.
On Windows, run this in PowerShell:
irm https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/refs/heads/main/scripts/install.ps1 | iexThe Windows installer downloads and verifies the matching released binary, then sets persistent per-user environment variables; open a new terminal afterwards.
The macOS/Linux installer retains an existing ANDROID_HOME; if it is not
set, it asks for an existing Android SDK directory and configures
ANDROID_HOME and ANDROID_SDK_ROOT to that location. Pass
--android-home PATH for non-interactive installation. It adds the SDK's
platform-tools and command-line-tools directories to PATH. Use
--aem-home PATH on macOS/Linux or -AemHome PATH on Windows to use another
AEM directory. By default, both installers use the latest release. Pass
--version 1.2.3 on macOS/Linux or -Version 1.2.3 on Windows to install a
specific release.
The uninstallers remove the AEM executable and the environment integration but retain installed runtimes and SDKs by default:
./scripts/uninstall.sh.\scripts\uninstall.ps1Pass --remove-data (macOS/Linux) or -RemoveData (Windows) to also delete
the selected AEM_HOME directory and every AEM-managed runtime and SDK.
Tagged releases publish standalone archives for Linux, macOS, and Windows
(AMD64 and ARM64 where supported). Download the archive matching your machine
from the GitHub Releases page,
then verify it against the published checksums.txt before adding the extracted
directory to your PATH. Use aem version to see the installed build version,
commit, and build date.
From a local AEM checkout, build and install the executable without downloading
a release from source control. It installs AEM in AEM_HOME/bin and configures
the same environment integration as the release installer:
./scripts/build.sh --android-home /path/to/Android/sdkSource builds bundle the version reported by git describe (derived from the
nearest tag), the commit, and the build date, so aem version identifies the
exact checkout the executable came from.
Pass --aem-home PATH, --profile PATH, or --no-profile as needed:
./scripts/build.sh --aem-home "$HOME/.aem-dev" --no-profile --android-home /path/to/Android/sdkOn Windows, run the PowerShell equivalent:
.\scripts\build.ps1It installs aem.exe in AEM_HOME\bin and configures the same persistent
user environment variables as install.ps1. Pass -AemHome PATH to choose a
different AEM home:
.\scripts\build.ps1 -AemHome C:\Users\you\.aem-devRun:
aem --helpto see available commands.
Create an aem.json template:
aem initThe command opens a keyboard-driven terminal UI. Use ↑/↓ (or j/k) to move,
Enter to select a Node.js or Java version, and Space to toggle detected Android
SDK components before confirming them with Enter. It then prints the path it
created. If you skip every option, the initial contents are valid and
intentionally minimal:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"runtime": {},
"android": {},
"hooks": {}
}The generated configuration can then be reviewed and committed to source control.
aem init only creates the project configuration. It does not install the dependencies.
For automation, use aem init --non-interactive to create the minimal template
without prompts.
Use:
aem setupto install and configure the environment defined by the generated aem.json.
aem available node
aem available javaaem install node 20
aem install java 17aem use node 20.11.1
aem use java 17.0.15aem list node
aem list java
aem list androidaem uninstall node 20.11.1
aem uninstall java 17.0.15
aem uninstall android "platforms;android-37.1"For safety, AEM refuses to remove a runtime selected through aem use or
aem setup. Switch to another version first.
aem currentaem doctorAEM keeps the version from the build tag bundled in the executable; check it
with aem version. To upgrade the executable itself in place:
aem update --check # report whether a newer release exists
aem update # download, verify, and install the newest release
aem update --version 1.2.3 # switch to a specific releaseThe update targets the release assets published on the project's GitHub
Releases page for the detected operating system and architecture, verifies the
archive against the release checksums, and then swaps the running executable.
The previous executable is restored automatically if the swap fails. Reinstall
the same version, downgrade, or replace a development build by adding
--force.
aem setupAEM searches for the nearest aem.json in the current directory or one of its parent directories.
If a requested dependency is missing, AEM downloads and installs it automatically.
| Command | Description |
|---|---|
aem init |
Interactively create and validate a canonical aem.json |
aem setup |
Install and configure the environment defined by aem.json |
aem install |
Install a Node/Java version or raw Android SDK package ID |
aem list |
List installed versions |
aem use |
Switch the active version |
aem available |
List available remote versions |
aem uninstall |
Remove an inactive runtime or Android SDK package |
aem current |
Show currently active runtimes |
aem doctor |
Inspect local AEM environment |
aem update |
Self-update the aem executable to the newest release |
Commands and flags may evolve during development. Run
aem --helpfor the latest usage information.
A project can define its development environment using an aem.json file.
The configuration is declarative:
The developer defines what versions the project needs. AEM determines how to install and configure them.
AEM does not attempt to determine the React Native version or infer dependency versions from the framework.
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"runtime": {
"node": "20.19.4",
"java": "17"
},
"android": {
"platforms": [
"35"
],
"buildTools": [
"35.0.0"
],
"ndk": [
"27.0.12077973"
],
"cmake": [
"3.22.1"
],
"systemImages": [
{
"apiLevel": 35,
"variant": "google_apis",
"architecture": "arm64-v8a"
}
]
},
"hooks": {
"preSetup": [],
"postSetup": []
}
}The aem.json file should normally be committed to source control so that the project's environment requirements can be reproduced by other developers and CI systems.
$schema points editors and JSON tooling to the AEM schema. runtime,
android, and hooks are optional, so a project can declare only the pieces
it needs.
Older configuration files used top-level node and jdk, plus Android
sdk and build-tool. AEM continues to read those fields and reports a
migration notice during setup, but new configurations must use the canonical
shape:
| Legacy field | Canonical field |
|---|---|
node |
runtime.node |
jdk |
runtime.java |
android.sdk |
android.platforms |
android.build-tool |
android.buildTools |
Do not mix legacy and canonical names in the same configuration; that is rejected to avoid ambiguous setup results.
Projects do not need to define every available dependency.
For example, a project may only require Node.js:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"runtime": {
"node": "20.19.4"
}
}Or Node.js and Java:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"runtime": {
"node": "20.19.4",
"java": "17"
}
}Android components can be added when required:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"runtime": {
"node": "20.19.4",
"java": "17"
},
"android": {
"platforms": [
"35"
],
"buildTools": [
"35.0.0"
],
"ndk": [
"27.0.12077973"
]
}
}AEM should only configure the components explicitly declared by the developer, along with any underlying prerequisites required to install them.
Android components are defined using human-readable configuration rather than raw Android sdkmanager package identifiers.
Specify Android API levels:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"android": {
"platforms": [
"34",
"35"
]
}
}AEM translates:
"34"
into the Android package:
platforms;android-34
Specify Build Tools versions:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"android": {
"buildTools": [
"34.0.0",
"35.0.0"
]
}
}AEM translates:
"34.0.0"
into:
build-tools;34.0.0
Specify NDK versions:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"android": {
"ndk": [
"25.1.8937393",
"27.0.12077973"
]
}
}AEM translates:
"25.1.8937393"
into:
ndk;25.1.8937393
Specify CMake versions:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"android": {
"cmake": [
"3.22.1"
]
}
}AEM translates:
"3.22.1"
into:
cmake;3.22.1
System images use structured configuration because the Android package identifier contains multiple pieces of information.
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"android": {
"systemImages": [
{
"apiLevel": 34,
"variant": "google_apis",
"architecture": "arm64-v8a"
}
]
}
}AEM translates this configuration into:
system-images;android-34;google_apis;arm64-v8a
The configuration fields are:
| Field | Description |
|---|---|
apiLevel |
Android API level |
variant |
System image variant, such as google_apis |
architecture |
System image CPU architecture |
package |
Optional exact SDK package ID for preview versions, such as android-37.0 |
The raw sdkmanager identifier is normally an implementation detail of the Android provider. aem init adds package only when an Android preview image needs an exact identifier, such as system-images;android-37.0;....
AEM automatically ensures that the Android command-line tooling required to perform SDK setup is available.
This may include:
- Android SDK Command-line Tools
sdkmanager- Android Platform Tools
These are considered infrastructure required by the Android provider rather than project-specific dependencies that developers need to declare in every aem.json.
During setup, AEM uses the Android SDK command-line tooling to install the requested Android components.
When any Android component is requested, AEM also installs platform-tools as
the shared SDK prerequisite. Re-running setup only passes locally missing
components to sdkmanager; it does not use the Android repository to resolve
or validate already installed local package directories.
Use the same list command used for runtimes to see locally installed Android package identifiers:
aem list androidaem list android reads the SDK selected through ANDROID_HOME (or
ANDROID_SDK_ROOT) and prints every locally installed package. It reads Android
package metadata, so packages installed outside AEM—such as emulator, sources,
and extras—are included as well.
aem doctor includes that SDK path, an installed-component count, and the
component inventory. Android is not an active runtime, so it is intentionally
absent from aem current.
The optional hooks object defines lifecycle hooks around aem setup:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"hooks": {
"preSetup": [],
"postSetup": []
}
}preSetup runs before dependency installation and activation; postSetup runs
after setup completes. Both fields are ordered arrays of shell commands that
run from the directory containing aem.json. A failed hook stops setup and is
reported as an error. Leave a field as an empty array when no hook is required.
When running:
aem setupAEM:
- Searches for the nearest
aem.json. - Loads and validates the configuration.
- Detects the host operating system.
- Detects the host architecture.
- Checks existing runtime installations and the configured Android SDK.
- Installs missing runtimes.
- Installs missing Android components.
- Activates the requested runtime versions.
- Configures the environment.
- Reports the resulting environment.
The setup operation should be idempotent.
Running:
aem setupmultiple times should not reinstall components that are already correctly installed.
AEM stores managed Node.js and Java runtimes inside a dedicated directory.
Android packages are installed in the SDK selected through ANDROID_HOME.
If AEM_HOME is not configured, the default location is:
macOS / Linux
~/.aem
Windows
%USERPROFILE%\.aem
AEM-managed installations may be organized approximately as:
~/.aem/
├── bin/
│ └── aem
├── current/
│ ├── node
│ └── java
├── sys_installed/
│ ├── node/
│ └── java/
├── tmp/
└── versions.json (legacy activation state, when present)
The exact internal structure may change as the project evolves.
AEM uses stable paths for active versions.
For example:
~/.aem/current/node
~/.aem/current/java
These paths point to the currently selected versions.
This allows the user's shell configuration to remain stable even when the active version changes.
For example:
~/.aem/current/node
↓
~/.aem/versions/node/20.11.1
After switching versions:
~/.aem/current/node
↓
~/.aem/versions/node/22.14.0
The platform installers above configure these values automatically. For a manual setup, add the following to your shell profile:
export AEM_HOME="${AEM_HOME:-$HOME/.aem}"
export JAVA_HOME="$AEM_HOME/current/java"
if [ "$(uname -s)" = "Darwin" ]; then
export JAVA_HOME="$JAVA_HOME/Contents/Home"
fi
export ANDROID_HOME="/path/to/your/Android/sdk"
export ANDROID_SDK_ROOT="$ANDROID_HOME"
export PATH="$AEM_HOME/bin:$AEM_HOME/current/node/bin:$JAVA_HOME/bin:$ANDROID_HOME/platform-tools:$ANDROID_HOME/cmdline-tools/latest/bin:$PATH"The macOS/Linux installer adds a clearly marked, removable block to the chosen
shell profile; scripts/uninstall.sh removes only that block. The Windows
installer changes only per-user environment variables and records their prior
values so scripts/uninstall.ps1 can restore them.
aem use and aem setup change stable links below $AEM_HOME/current; they
do not modify a terminal that is already open. Configure the shell once to use
those stable paths, then opening a new shell (or sourcing its configuration)
picks up every subsequent runtime switch.
On macOS and Linux, ./scripts/install.sh writes $AEM_HOME/env.sh and adds a
marked source block to the selected Bash or Zsh profile. It exports:
PATH=$AEM_HOME/bin:$AEM_HOME/current/node/bin:$JAVA_HOME/bin:...
JAVA_HOME=$AEM_HOME/current/java
ANDROID_HOME=<your existing Android SDK>
ANDROID_SDK_ROOT=<your existing Android SDK>
On macOS, Azul archives may be app bundles, so the managed script sets
JAVA_HOME to $AEM_HOME/current/java/Contents/Home. AEM normalizes wrapped
Zulu 8 bundles to provide the same stable path used by newer JDKs. The installer
retains an existing Android SDK rather than moving it. For another shell, source
the file from a POSIX-compatible startup wrapper, or source it manually before
running tools that require Node or Java.
On Windows, scripts/install.ps1 persists the same values for the current user
and adds %AEM_HOME%\bin, %AEM_HOME%\current\node,
%AEM_HOME%\current\java\bin, and Android tool directories to the user
Path. Restart PowerShell, Command Prompt, Windows Terminal, and IDE terminals
after running it. The Windows installer uses %AEM_HOME%\current\android as
the managed SDK directory unless ANDROID_HOME is overridden.
If you configure links manually, point AEM_NODE_SYMLINK and
AEM_JAVA_SYMLINK at safe, dedicated directory-link paths. AEM refuses to
replace a regular file or directory at either path. On Windows it falls back to
a directory junction when symbolic links are unavailable.
Node.js binaries and release metadata come from the official Node.js distribution. Java JDKs come from Azul Zulu's public metadata API. Android components are installed through Google's Android command-line tools. AEM selects artifacts for the detected operating system and architecture; an unsupported target fails instead of silently choosing another platform.
Downloads are staged in $AEM_HOME/tmp and removed after an installation.
AEM has no persistent offline artifact cache yet, so a missing runtime or
Android package requires network access. Valid installed versions are reused,
making aem setup safe to repeat without re-downloading them.
AEM never upgrades a project runtime merely because a newer release exists.
Update the version in aem.json (or explicitly run aem install) and then
activate or set up that version. Update AEM itself with aem update, or by
installing a newer verified release archive or rebuilding a newer source
checkout; verify the result with aem version.
Run aem doctor first. It reports the AEM home, install location, active
runtime links, and discovered Android components.
- A runtime is installed but a command is not found: start a new terminal,
or source
$AEM_HOME/env.shon macOS/Linux. Confirm the active runtime withaem currentand ensure the relevantcurrentpath precedes a system installation inPATH. - Java is selected but build tools cannot find it: check
JAVA_HOME. On macOS it normally ends incurrent/java/Contents/Home; on Linux and Windows it normally ends incurrent/java. - Android tooling is missing: set
ANDROID_HOMEandANDROID_SDK_ROOTto the SDK AEM manages or the SDK selected during installation, then ensureplatform-toolsandcmdline-tools/latest/binare inPATH. - Windows activation cannot create a link: use a dedicated link path and rerun the command. AEM attempts a symbolic link and then a directory junction; enable Developer Mode or use an elevated shell only if both fail.
- A download fails: check network/proxy settings, rerun with
aem --debug, and retry. Staging files in$AEM_HOME/tmpare safe to remove after AEM is no longer running.
AEM is a project-defined environment manager, not a framework compatibility manager.
For example, if a developer specifies:
{
"$schema": "https://raw.githubusercontent.com/Adaptive-Cloud/aem-go/main/aem.schema.json",
"runtime": {
"node": "20.19.4",
"java": "17"
},
"android": {
"platforms": [
"35"
],
"buildTools": [
"35.0.0"
],
"ndk": [
"27.0.12077973"
]
}
}AEM should install those requested versions.
AEM should not:
- Detect the React Native version.
- Inspect
package.jsonto determine framework requirements. - Automatically upgrade versions.
- Automatically downgrade versions.
- Replace requested versions with versions it considers more compatible.
- Infer dependency versions from a framework.
The project configuration is the source of truth.
Contributions are welcome!
This project is a personal initiative to learn Go while building a practical development environment management tool.
If you'd like to contribute:
- Fork the repository.
- Create a feature branch:
git checkout -b feature/your-feature- Make your changes.
- Add or update tests.
- Commit your changes:
git commit -m "Add some feature"- Push your branch:
git push origin feature/your-feature- Open a Pull Request.
Please make sure to write tests and document significant changes.
AEM has two primary goals.
This project provides a practical way to learn and improve skills in:
- Go
- CLI development
- Cross-platform development
- API integration
- Package management
- Environment management
- Software distribution
Setting up development environments often involves repetitive manual work.
Developers may need to:
- Install a specific Node.js version.
- Install a specific JDK.
- Configure
JAVA_HOME. - Install Android SDK platforms.
- Install Build Tools.
- Install a particular NDK.
- Install CMake.
- Configure Android environment variables.
- Switch between different project environments.
AEM aims to automate this process.
A developer should be able to clone a project containing aem.json and run:
aem setupto have the environment described by the project configuration ready to use.
Potential future improvements include:
- CI/CD environment setup.
- Environment profiles.
- Improved configuration management.
- Additional runtime providers.
- More Android SDK components.
- Android Emulator management.
- Better cross-platform installers.
- Package manager integrations.
- Shell integration.
- Environment export/import.
- Improved caching and download management.
- Checksums and artifact verification.
- More sophisticated version management.
- Reproducible development environments.
Created by firmajml.
Feel free to open an issue for bugs, feature requests, suggestions, or questions.
Thank you for using Adaptive Environment Manager! 🚀