nitrogen_cli 0.4.4
nitrogen_cli: ^0.4.4 copied to clipboard
CLI for Nitrogen Modules. Scaffold new Flutter FFI plugins and generate native bridge code.
nitrogen_cli ⚡ #
CLI tool for Nitrogen plugins. Scaffold, generate, and link Nitrogen FFI plugins from the command line.

⚡ For Nitro docs visit: nitro.shreeman.dev
Installation #
# From this monorepo (local development):
dart pub global activate --source path packages/nitrogen_cli
# Or from pub.dev:
dart pub global activate nitrogen_cli
Add the Dart pub global bin to your PATH (one-time):
# ~/.zshrc or ~/.bashrc:
export PATH="$PATH:$HOME/.pub-cache/bin"
Running nitrogen without arguments launches an interactive TUI dashboard. From there you can init, generate, link, doctor, and update with live visual feedback.
Headless / CI mode #
Every command supports --no-ui. In headless mode all output is plain text prefixed with [nitro], [nitro:warn], or [nitro:error] — no ANSI codes, no interactive prompts.
TTY auto-detection: when stdout is not a terminal (piped output, CI runner), --no-ui activates automatically — you never need to pass it explicitly in CI.
nitrogen <command> --no-ui
Commands #
nitrogen init #
Scaffolds a complete Nitrogen plugin from scratch with pre-wired native configurations.
nitrogen init # interactive TUI form
nitrogen init --name my_plugin # skip form, show progress TUI
nitrogen init --no-ui --name my_plugin # headless / CI
nitrogen init --no-ui --name my_plugin \
--org com.example \
--platforms android,ios,macos
Flags:
| Flag | Default | Description |
|---|---|---|
--name, -n |
— | Plugin name (skips interactive form) |
--org |
com.example |
Android/iOS organisation identifier |
--dir, -d |
. |
Parent directory to create the plugin in |
--platforms, -p |
android,ios,macos,windows,linux |
Comma-separated target platforms |
--no-ui |
false |
Headless output. Requires --name |
What it creates:
| File | Description |
|---|---|
lib/src/<name>.native.dart |
Starter spec — define your API here |
ios/Classes/<Name>Impl.swift |
Starter Swift implementation |
ios/Classes/Swift<Name>Plugin.swift |
Flutter plugin registrar |
ios/<name>.podspec |
Pre-configured: Swift 5.9, iOS 13.0, C++17, HEADER_SEARCH_PATHS |
ios/<name>/Package.swift |
Swift Package Manager support (nested Flutter 3.41+ layout) |
android/.../<Name>Impl.kt |
Starter Kotlin implementation |
android/.../<Name>Plugin.kt |
Flutter plugin registrar |
src/CMakeLists.txt |
NDK build file |
pubspec.yaml |
Pre-wired with nitro and nitro_generator |
You only ever edit: the spec, the Kotlin impl, and the Swift impl (or a single C++ impl). Everything else is generated.
nitrogen generate #
Runs flutter pub get + build_runner build and syncs all generated files to their native destinations.
nitrogen generate
# Headless / CI — no ANSI codes, plain [nitro] prefix lines
nitrogen generate --no-ui
# Treat spec validation warnings as errors (exit code 2)
nitrogen generate --fail-on-warn
Flags:
| Flag | Default | Description |
|---|---|---|
--no-ui |
false |
Headless plain-text output |
--fail-on-warn |
false |
Exit code 2 if spec has warnings |
Exit codes:
| Code | Meaning |
|---|---|
0 |
Success |
1 |
Generation error |
2 |
Spec warnings present and --fail-on-warn was passed |
What it produces (per .native.dart spec):
| Output | Description |
|---|---|
lib/src/*.g.dart |
Dart FFI implementation |
lib/src/generated/kotlin/*.bridge.g.kt |
Kotlin JNI bridge (NativeImpl.kotlin) |
lib/src/generated/swift/*.bridge.g.swift |
Swift @_cdecl bridge (NativeImpl.swift) |
lib/src/generated/cpp/*.bridge.g.h |
C header (all modes) |
lib/src/generated/cpp/*.bridge.g.cpp |
C++ bridge (all modes) |
lib/src/generated/cmake/*.CMakeLists.g.txt |
CMake fragment (all modes) |
lib/src/generated/cpp/*.native.g.h |
Abstract C++ interface (NativeImpl.cpp) |
lib/src/generated/cpp/test/*.mock.g.h |
GoogleMock stub (NativeImpl.cpp) |
lib/src/generated/cpp/test/*.test.g.cpp |
Test starter (NativeImpl.cpp) |
NativeImpl.cpp awareness: .bridge.g.swift files that contain only a "Not applicable" placeholder are never copied to ios/Classes/. Instead, the generated .native.g.h headers are synced there so Clang can resolve them during iOS builds.
After generation, nitrogen generate also runs pod install in any ios/ directory it finds.
CI example:
# .github/workflows/build.yml
- name: Generate Nitrogen bindings
run: |
dart pub global activate nitrogen_cli
nitrogen generate --no-ui --fail-on-warn # exit 2 if spec has warnings
nitrogen link #
Wires native build files (CMake, Podspec, Kotlin plugin, Swift plugin, .clangd) to the generated code.
nitrogen link # interactive TUI with confirmation prompt
nitrogen link --yes # skip confirmation prompt, show TUI
nitrogen link --no-ui # headless (implies --yes)
Flags:
| Flag | Default | Description |
|---|---|---|
--yes, -y |
false |
Skip the "Proceed?" confirmation prompt |
--no-ui |
false |
Headless plain-text output (implies --yes) |
What it wires:
- Adds
include(...)for each.CMakeLists.g.txtintosrc/CMakeLists.txt - Adds
System.loadLibrary("lib")toandroid/.../Plugin.kt - Skips
JniBridge.register(...)for all-cpp plugins - Sets
HEADER_SEARCH_PATHS+DEFINES_MODULEin the iOS.podspec(and macOS.podspecwhenmacos/exists) - Injects bridge registration into
ios/*Plugin.swiftandmacos/*Plugin.swiftfor Swift/Kotlin modules - Skips Swift bridge registration step for all-cpp plugins
- Creates
ios/Classes/dart_api_dl.candmacos/Classes/dart_api_dl.cforwarders if missing - Copies
nitro.hto bothios/Classes/andmacos/Classes/ - Updates
.clangdto includegenerated/cpp/test/for GoogleMock IDE support when cpp modules exist - Strips redundant
#include "*.bridge.g.cpp"directives fromsrc/files
nitrogen doctor #
Deep health check of every layer of your native build. Read-only — no files are changed.
nitrogen doctor # interactive TUI
nitrogen doctor --no-ui # headless, one line per check
Flags:
| Flag | Default | Description |
|---|---|---|
--no-ui |
false |
Plain-text output with [nitro:ok] / [nitro:warn] / [nitro:error] prefixes |
Sections checked:
| Section | Key checks |
|---|---|
| System Toolchain | clang++, Xcode, Android NDK, Java |
| pubspec.yaml | nitro, build_runner, nitro_generator deps; iOS and macOS plugin platform config |
| Generated Files | Every expected output file — present, not stale |
| CMakeLists.txt | NITRO_NATIVE, dart_api_dl.c, add_library(lib) target |
| Android | kotlin-android, kotlinOptions, generated/kotlin sourceSets, System.loadLibrary, JniBridge.register |
| iOS | .podspec headers/C++17, Swift version, dart_api_dl.c, nitro.h, NITRO_EXPORT, .bridge.g.mm count |
| macOS | .podspec headers/C++17, Swift version, dart_api_dl.c, nitro.h, NITRO_EXPORT, .bridge.g.mm count, Swift plugin registration |
| NativeImpl.cpp (cpp modules only) | ${lib}_register_impl wired up, .clangd includes test dir |
NativeImpl.cpp awareness:
- Android: when all specs use
NativeImpl.cpp, Kotlin JNI bridge checks are shown asℹ info(not required) instead of errors. - iOS: Registry.register check skipped; checks for
.native.g.hheaders inios/Classes/instead; no.bridge.g.mmwarning. - Generated files:
.bridge.g.kt/.bridge.g.swiftshown asℹ info(placeholder) for cpp modules;.native.g.h,.mock.g.h,.test.g.cppchecked as required outputs.
Exit codes: 0 = all checks pass, 1 = one or more errors (suitable for CI).
# .github/workflows/build.yml
- name: Nitrogen health check
run: |
dart pub global activate nitrogen_cli
nitrogen doctor --no-ui
nitrogen migrate #
Migrates a CocoaPods-only plugin to the Swift Package Manager nested layout (Flutter 3.41+).
nitrogen migrate # interactive TUI with preview
nitrogen migrate --dry-run # preview changes without writing files
nitrogen migrate --no-backup # skip backup step
nitrogen migrate --no-ui # headless, skips interactive confirmation
Flags:
| Flag | Default | Description |
|---|---|---|
--backup |
true |
Create a .nitrogen_backup_<ts>/ snapshot before migrating |
--dry-run |
false |
Show what would change without writing any files |
--no-ui |
false |
Headless plain-text output (skips confirmation prompt) |
What it does:
- Optionally backs up existing
ios/*.podspecandexample/ios/Podfile - Creates
ios/<name>/Package.swift(Flutter 3.41+ nested SPM layout) - Creates
macos/<name>/Package.swiftwhenmacos/exists - Runs
pod deintegrate+pod installin the example app
nitrogen watch #
Runs build_runner watch and re-links generated files on every change.
nitrogen watch # streaming TUI output
nitrogen watch --no-ui # headless, raw build_runner lines with [nitro] prefix
Flags:
| Flag | Default | Description |
|---|---|---|
--no-ui |
false |
Headless plain-text output |
nitrogen clean #
Deletes all Nitrogen-generated files and the build_runner cache.
nitrogen clean # interactive output
nitrogen clean --no-ui # headless
Flags:
| Flag | Default | Description |
|---|---|---|
--no-ui |
false |
Headless plain-text output |
What it removes: all *.g.dart, *.bridge.g.swift, *.bridge.g.kt, *.bridge.g.cpp, *.bridge.g.h, *.bridge.g.mm, *.CMakeLists.g.txt, *.native.g.h, *.mock.g.h, *.test.g.cpp files and the .dart_tool/build/ cache.
nitrogen update #
Self-updates the nitrogen CLI to the latest version on pub.dev (or git pull if path-activated).
nitrogen update # interactive TUI
nitrogen update --no-ui # headless
Flags:
| Flag | Default | Description |
|---|---|---|
--no-ui |
false |
Headless plain-text output |
What it does:
- Checks the current activation (
dart pub global list) - Fetches the latest version from pub.dev
- Runs
dart pub global activate nitrogen_cli(hosted) orgit pull --ff-only(path-activated)
nitrogen open #
Opens the generated spec file in your editor.
nitrogen open # interactive editor picker
nitrogen open --no-ui # headless
Flags:
| Flag | Default | Description |
|---|---|---|
--no-ui |
false |
Headless plain-text output |
Platform Targeting #
Each platform is configured independently via the @NitroModule annotation. All three platforms can be mixed and matched:
// iOS + Android Swift/Kotlin, macOS via direct C++
@NitroModule(lib: 'sensor', ios: NativeImpl.swift, android: NativeImpl.kotlin, macos: NativeImpl.cpp)
abstract class SensorModule extends HybridObject { ... }
// All three platforms using direct C++ (same implementation everywhere)
@NitroModule(lib: 'math', ios: NativeImpl.cpp, android: NativeImpl.cpp, macos: NativeImpl.cpp)
abstract class Math extends HybridObject { ... }
// iOS + macOS Swift, Android Kotlin
@NitroModule(lib: 'plugin', ios: NativeImpl.swift, android: NativeImpl.kotlin, macos: NativeImpl.swift)
abstract class MyPlugin extends HybridObject { ... }
Note:
macos: NativeImpl.kotlinis not valid — Kotlin is not a native macOS language. The generator will emit anINVALID_MACOS_IMPLerror at build time.
NativeImpl.cpp Workflow #
For plugins where both platforms use direct C++:
// lib/src/math.native.dart
@NitroModule(lib: 'math', ios: NativeImpl.cpp, android: NativeImpl.cpp, macos: NativeImpl.cpp)
abstract class Math extends HybridObject {
static final Math instance = _MathImpl();
double add(double a, double b);
}
nitrogen generate # → math.native.g.h (abstract C++ interface)
nitrogen link # → wires CMake, podspec, .clangd
// src/HybridMath.cpp (you write this)
#include "math.native.g.h"
class HybridMathImpl : public HybridMath {
public:
double add(double a, double b) override { return a + b; }
};
static HybridMathImpl g_math;
// Auto-register on shared library load — no manual init call needed.
// (Generated by nitrogen link via linkCppImplStubs)
__attribute__((constructor))
static void math_auto_register() {
math_register_impl(&g_math);
}
nitrogen doctor # → checks register_impl is wired, headers synced, .clangd up to date
Spec Example (Swift/Kotlin path) #
// lib/src/sensor.native.dart
import 'package:nitro/nitro.dart';
part 'sensor.g.dart';
@HybridEnum(startValue: 0)
enum DeviceStatus { idle, busy, error }
@HybridStruct(packed: true)
class SensorData {
final double temperature;
final double humidity;
const SensorData({required this.temperature, required this.humidity});
}
@NitroModule(lib: 'sensor', ios: NativeImpl.swift, android: NativeImpl.kotlin, macos: NativeImpl.swift)
abstract class SensorModule extends HybridObject {
static final SensorModule instance = _SensorModuleImpl();
DeviceStatus getStatus();
void updateSensors(SensorData data);
@NitroStream(backpressure: Backpressure.dropLatest)
Stream<SensorData> get sensorStream;
}
Requirements #
| Tool | Minimum |
|---|---|
| Dart SDK | 3.3.0+ |
| Flutter SDK | 3.22.0+ |
| Android NDK | 26.1+ |
| Kotlin | 1.9.0+ |
| iOS Deployment Target | 13.0+ |
| Xcode | 15.0+ |
Related #
- nitro — Runtime (base classes, annotations, helpers)
- nitro_generator — build_runner code generator
- Getting started guide — step-by-step walkthrough