nitrogen_cli 0.4.4 copy "nitrogen_cli: ^0.4.4" to clipboard
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.

Nitrogen Dashboard

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

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.txt into src/CMakeLists.txt
  • Adds System.loadLibrary("lib") to android/.../Plugin.kt
  • Skips JniBridge.register(...) for all-cpp plugins
  • Sets HEADER_SEARCH_PATHS + DEFINES_MODULE in the iOS .podspec (and macOS .podspec when macos/ exists)
  • Injects bridge registration into ios/*Plugin.swift and macos/*Plugin.swift for Swift/Kotlin modules
  • Skips Swift bridge registration step for all-cpp plugins
  • Creates ios/Classes/dart_api_dl.c and macos/Classes/dart_api_dl.c forwarders if missing
  • Copies nitro.h to both ios/Classes/ and macos/Classes/
  • Updates .clangd to include generated/cpp/test/ for GoogleMock IDE support when cpp modules exist
  • Strips redundant #include "*.bridge.g.cpp" directives from src/ 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.h headers in ios/Classes/ instead; no .bridge.g.mm warning.
  • Generated files: .bridge.g.kt / .bridge.g.swift shown as ℹ info (placeholder) for cpp modules; .native.g.h, .mock.g.h, .test.g.cpp checked 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:

  1. Optionally backs up existing ios/*.podspec and example/ios/Podfile
  2. Creates ios/<name>/Package.swift (Flutter 3.41+ nested SPM layout)
  3. Creates macos/<name>/Package.swift when macos/ exists
  4. Runs pod deintegrate + pod install in 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:

  1. Checks the current activation (dart pub global list)
  2. Fetches the latest version from pub.dev
  3. Runs dart pub global activate nitrogen_cli (hosted) or git 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.kotlin is not valid — Kotlin is not a native macOS language. The generator will emit an INVALID_MACOS_IMPL error 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+

1
likes
140
points
467
downloads

Documentation

Documentation
API reference

Publisher

verified publishershreeman.dev

Weekly Downloads

CLI for Nitrogen Modules. Scaffold new Flutter FFI plugins and generate native bridge code.

Homepage
Repository (GitHub)
View/report issues

Topics

#cli #codegen #ffi #flutter #native

Funding

Consider supporting this project:

buymeacoffee.com

License

MIT (license)

Dependencies

args, nocterm, nocterm_unrouter, path, watcher

More

Packages that depend on nitrogen_cli