nuon CLI. Once installed, an extension called policies is available as
nuon policies, just like a built-in command.
The extension system was heavily inspired by
GitHub CLI extensions.
Compiled vs interpreted extensions
Extensions come in two flavors:- Compiled extensions ship precompiled binaries via GitHub Releases. The CLI downloads the correct binary for your platform during install. This is the standard distribution model for production extensions.
-
Interpreted extensions are cloned as source and run directly. The CLI supports two interpreted types:
- Script: a single executable file (bash, Python, etc.) with a shebang line
- Python: a uv-managed project executed via
uv run
Quick start
Managing extensions
All extension management commands live undernuon extensions (alias: nuon ext).
Browsing available extensions
nuonco GitHub organization for repositories matching the nuon-ext-* naming convention and shows
which ones you already have installed. Extensions can be installed from any GitHub organization, but browse lists
Nuon-authored extensions by default.
Installing an extension
| Input | Resolved to |
|---|---|
policies | nuonco/nuon-ext-policies (latest) |
nuon-ext-policies | nuonco/nuon-ext-policies (latest) |
nuonco/nuon-ext-policies | nuonco/nuon-ext-policies (latest) |
myorg/nuon-ext-foo | myorg/nuon-ext-foo (latest) |
nuonco/nuon-ext-demo@main | nuonco/nuon-ext-demo at branch main |
demo@v1.0.0 | nuonco/nuon-ext-demo at tag v1.0.0 |
demo@abc123 | nuonco/nuon-ext-demo at commit abc123 |
./nuon-ext-my-tool | Local directory (symlink) |
nuonco GitHub organization. To install an extension from
another org, use the full org/nuon-ext-name format.
How install works
The install behavior depends on the extension type:-
Compiled extensions: The CLI checks the latest GitHub Release for a platform-specific binary asset (e.g.
nuon-ext-policies-darwin-arm64). If a match is found, it downloads the binary directly. No clone is needed. -
Interpreted extensions: If no release asset matches your platform, the CLI clones the repository and detects the
extension type from the directory contents (
pyproject.tomlfor python, executablenuon-ext-<name>for script).
@ref is specified (e.g. demo@main, nuonco/nuon-ext-demo@v1.0), the CLI always clones the repo at that exact
ref, skipping release detection entirely. This is the standard way to install interpreted extensions and to pin compiled
extensions to a specific source revision.
Local install
nuon-ext.toml and the directory name must match nuon-ext-<name>.
If the extension is already installed, the command fails and you will need to use the nuon ext upgrade command
instead. However, we strongly reccomend uninstalling and re-installing from source when the dev work is complete.
Listing installed extensions
--json for
machine-readable output.
Upgrading extensions
Removing an extension
Running an extension explicitly
exec subcommand is an escape hatch for cases where an extension name conflicts with a built-in command. In most
cases, you can run extensions directly as top-level commands (e.g. nuon policies).
How extensions work
Top-level commands
When the CLI starts, it scans the extensions directory and registers each installed extension as a top-level command. This meansnuon policies works the same as nuon ext exec policies.
All flags and arguments are passed through to the extension unchanged.
Environment variables
When running an extension, the CLI provides context through environment variables:| Variable | Description |
|---|---|
NUON_API_URL | API endpoint URL |
NUON_ORG_ID | Current organization context |
NUON_APP_ID | Current app context (if set) |
NUON_INSTALL_ID | Current install context (if set) |
NUON_API_TOKEN | Authentication token |
NUON_EXT_NAME | Extension name |
NUON_EXT_DIR | Path to the extension’s local directory |
NUON_CONFIG_FILE | Path to the Nuon config file |
Authentication
Extensions declare their auth requirements in anuon-ext.toml manifest. If an extension requires an API token or org
context and one isn’t configured, the CLI prints a warning before running the extension. The extension still runs and is
allowed to handle the missing credentials on its own.
Local storage
Extensions are stored under~/.config/nuon/extensions/. Each extension gets its own directory:
Creating an extension
Every extension needs two things: a GitHub repository namednuon-ext-<name> and a nuon-ext.toml manifest at the
root. Beyond that, the approach differs depending on whether you’re building a compiled or interpreted extension.
Repository name
The repository must use thenuon-ext- prefix. The rest becomes the command name:
Extension manifest
Every extension repository must include anuon-ext.toml file at the root:
nuon-ext.toml
| Field | Required | Description |
|---|---|---|
extension.name | Yes | Must match the repo suffix after nuon-ext- |
extension.description | Yes | Shown in list and browse output |
extension.min_cli_version | No | Minimum CLI version required |
extension.auth.requires_token | No | Warn if no API token is configured |
extension.auth.requires_org | No | Warn if no org is selected |
Compiled extensions (binary)
Compiled extensions ship precompiled binaries for each platform via GitHub Releases. This is the best choice when you want fast startup, no runtime dependencies, and a clean upgrade path vianuon ext upgrade.
Create GitHub releases with assets following this naming scheme:
Interpreted extensions (script and python)
Interpreted extensions are cloned as source and run directly. They’re simpler to set up and great for tooling that doesn’t need to be compiled. For example: documentation generators, linters, config validators, and similar utilities. The CLI supports two interpreted types:| Type | Detection | Execution | Requires |
|---|---|---|---|
| script | Executable nuon-ext-<name> at repo root | Run script directly | Nothing extra |
| python | pyproject.toml at repo root | uv run nuon-ext-<name> | uv on the host |
Script extensions
Place a single executable file namednuon-ext-<name> at the repo root with a shebang line:
nuon-ext-hello
chmod +x).
Python extensions
Python extensions use a standardpyproject.toml with a console script entry point. The CLI runs them via
uv run nuon-ext-<name>, so uv manages the virtualenv and dependencies automatically.
A minimal project structure:
pyproject.toml entry point like:
@ref to pin a branch: