package.json that declares which entry files power which tiers. Aether reads this manifest at load time to discover entry points for the Pi agent layer, the Script Mod runtime, and the native DEX loader. Only the fields relevant to the tiers you use are required — unused tier fields are simply absent.
Full package.json example
The example below activates all three tiers from a single package:package.json
Fields reference
string
An npm-style package name that identifies your extension. Use lowercase letters, numbers, and hyphens (for example,
my-aether-mod or @myorg/chat-enhancer). Aether uses this name in the Extensions list and when generating stable extension IDs; if omitted, it falls back to the package directory name.string
A semver version string (for example,
1.0.0). Increment this value when you publish updates so Aether and package managers can track changes.string[]
Array of paths to Pi extension entry files, relative to the package root. Each file is loaded by the Pi Coding Agent runtime. Export a factory function as
default to register Pi commands, tools, and hooks. Omit this field if your extension does not target the Pi agent layer.string[]
Array of paths to Aether Script Mod entry files, relative to the package root. Each file is loaded by the Aether Script Mod runtime (API v2). Export your factory as
activateAether or aether to register surfaces, components, pages, actions, and event handlers. The runtime also accepts a default export for entries explicitly listed here (useful for packages that target only this tier). These files reload without restarting the app when an extension reload is triggered. Omit this field if your extension does not use the Script Mod API.number | object
Script API compatibility. Use the exact integer
2, or an object such as { "min": 2, "max": 2, "allowNewer": false }. Missing declarations remain compatible for legacy packages. An incompatible or malformed declaration rejects the reload and keeps the previous working Script runtime active.number | object
Native API compatibility. The current Native API version is
1. Use an exact integer or the same min, max, and optional allowNewer range format as aether.api.string[]
Array of paths to
.dex, .apk, or jar/zip files that contain a compiled classes.dex, relative to the package root. Aether passes these paths to DexClassLoader at process startup. A plain JVM .class jar is not sufficient — run Android D8/R8 on your compiled output first. Omit the entire aether.native object if your extension does not include a Native Mod.string[]
Array of paths to directories containing
.so native libraries, relative to the package root. Aether adds these directories to the native library search path when loading the DEX classpath. Only required if your Native Mod uses JNI libraries.string[]
Array of fully-qualified Kotlin class names that implement
AetherNativeMod. Aether instantiates each class via the DEX classloader and calls onLoad(context) during process startup. Requires aether.native.classpath to contain the compiled classes.Script-only vs native-only packages
Script-only packages omit theaether.native object entirely. This is the most common extension format and can reload without restarting Aether:
package.json
pi field and the extensions field under aether. Include only aether.native:
package.json
Installation location
Imported extension packages live under~/.aether/extensions inside Aether’s managed Alpine filesystem. When you import a zip via Settings → Extensions → Import extension, Aether stages the package before replacing an existing copy. A bundled node_modules directory is used as-is. Otherwise, packages with runtime dependencies use npm ci --omit=dev when a lockfile is present and npm install --omit=dev when it is not. Dependency or reload failures restore the previous package. Packages installed through Aether’s package management are discovered through the Pi package manager.
Shared Pi + Aether entry point
When you share a single entry file betweenpi.extensions and aether.extensions, keep the Pi factory as the default export and export the Aether factory as activateAether or aether. The Script Mod runtime looks for activateAether or aether first; it falls back to default only for entries that are explicitly listed in aether.extensions (as opposed to implicitly discovered files).
index.ts
The
defineAetherExtension helper provides TypeScript types for the aether argument and is a no-op at runtime — it simply returns the factory function you pass in. Import it from @baimoqilin/aether-extension-api.