# ADR-014: Nushell Nickel Plugin - CLI Wrapper Architecture\n\n## Status\n\n**Accepted** - 2025-12-15\n\n## Context\n\nThe provisioning system integrates with Nickel for configuration management in advanced\nscenarios. Users need to evaluate Nickel files and work with their output in Nushell\nscripts. The `nu_plugin_nickel` plugin provides this integration.\n\nThe architectural decision was whether the plugin should:\n\n1. **Implement Nickel directly using pure Rust** (`nickel-lang-core` crate)\n2. **Wrap the official Nickel CLI** (`nickel` command)\n\n### System Requirements\n\nNickel configurations in provisioning use the **module system**:\n\n```\n# config/database.ncl\nimport "lib/defaults" as defaults\nimport "lib/validation" as valid\n\n{\n databases: {\n primary = defaults.database & {\n name = "primary"\n host = "localhost"\n }\n }\n}\n```\n\nModule system includes:\n\n- Import resolution with search paths\n- Standard library (`builtins`, stdlib packages)\n- Module caching\n- Complex evaluation context\n\n## Decision\n\nImplement the `nu_plugin_nickel` plugin as a **CLI wrapper** that invokes the external `nickel` command.\n\n### Architecture Diagram\n\n```\n┌─────────────────────────────┐\n│ Nushell Script │\n│ │\n│ nickel-export json /file │\n│ nickel-eval /file │\n│ nickel-format /file │\n└────────────┬────────────────┘\n │\n ▼\n┌─────────────────────────────┐\n│ nu_plugin_nickel │\n│ │\n│ - Command handling │\n│ - Argument parsing │\n│ - JSON output parsing │\n│ - Caching logic │\n└────────────┬────────────────┘\n │\n ▼\n┌─────────────────────────────┐\n│ std::process::Command │\n│ │\n│ "nickel export /file ..." │\n└────────────┬────────────────┘\n │\n ▼\n┌─────────────────────────────┐\n│ Nickel Official CLI │\n│ │\n│ - Module resolution │\n│ - Import handling │\n│ - Standard library access │\n│ - Output formatting │\n│ - Error reporting │\n└────────────┬────────────────┘\n │\n ▼\n┌─────────────────────────────┐\n│ Nushell Records/Lists │\n│ │\n│ ✅ Proper types │\n│ ✅ Cell path access works │\n│ ✅ Piping works │\n└─────────────────────────────┘\n```\n\n### Implementation Characteristics\n\n**Plugin provides**:\n\n- ✅ Nushell commands: `nickel-export`, `nickel-eval`, `nickel-format`, `nickel-validate`\n- ✅ JSON/YAML output parsing (serde_json → nu_protocol::Value)\n- ✅ Automatic caching (SHA256-based, ~80-90% hit rate)\n- ✅ Error handling (CLI errors → Nushell errors)\n- ✅ Type-safe output (nu_protocol::Value::Record, not strings)\n\n**Plugin delegates to Nickel CLI**:\n\n- ✅ Module resolution with search paths\n- ✅ Standard library access and discovery\n- ✅ Evaluation context setup\n- ✅ Module caching\n- ✅ Output formatting\n\n## Rationale\n\n### Why CLI Wrapper Is The Correct Choice\n\n| Aspect | Pure Rust (nickel-lang-core) | CLI Wrapper (chosen) |\n| -------- | ------------------------------- | ---------------------- |\n| **Module resolution** | ❓ Undocumented API | ✅ Official, proven |\n| **Search paths** | ❓ How to configure? | ✅ CLI handles it |\n| **Standard library** | ❓ How to access? | ✅ Automatic discovery |\n| **Import system** | ❌ API unclear | ✅ Built-in |\n| **Evaluation context** | ❌ Complex setup needed | ✅ CLI provides |\n| **Future versions** | ⚠️ Maintain parity | ✅ Automatic support |\n| **Maintenance burden** | 🔴 High | 🟢 Low |\n| **Complexity** | 🔴 High | 🟢 Low |\n| **Correctness** | ⚠️ Risk of divergence | ✅ Single source of truth |\n\n### The Module System Problem\n\nUsing `nickel-lang-core` directly would require the plugin to:\n\n1. **Configure import search paths**:\n\n ```rust\n // Where should Nickel look for modules?\n // Current directory? Workspace? System paths?\n // This is complex and configuration-dependent\n ```\n\n1. **Access standard library**:\n\n ```rust\n // Where is the Nickel stdlib installed?\n // How to handle different Nickel versions?\n // How to provide builtins?\n ```\n\n2. **Manage module evaluation context**:\n\n ```rust\n // Set up evaluation environment\n // Configure cache locations\n // Initialize type checker\n // This is essentially re-implementing CLI logic\n ```\n\n3. **Maintain compatibility**:\n - Every Nickel version change requires review\n - Risk of subtle behavioral differences\n - Duplicate bug fixes and features\n - Two implementations to maintain\n\n### Documentation Gap\n\nThe `nickel-lang-core` crate lacks clear documentation on:\n\n- ❓ How to configure import search paths\n- ❓ How to access standard library\n- ❓ How to set up evaluation context\n- ❓ What is the public API contract?\n\nThis makes direct usage risky. The CLI is the documented, proven interface.\n\n### Why Nickel Is Different From Simple Use Cases\n\n**Simple use case** (direct library usage works):\n\n- Simple evaluation with built-in functions\n- No external dependencies\n- No modules or imports\n\n**Nickel reality** (CLI wrapper necessary):\n\n- Complex module system with search paths\n- External dependencies (standard library)\n- Import resolution with multiple fallbacks\n- Evaluation context that mirrors CLI\n\n## Consequences\n\n### Positive\n\n- **Correctness**: Module resolution guaranteed by official Nickel CLI\n- **Reliability**: No risk from reverse-engineering undocumented APIs\n- **Simplicity**: Plugin code is lean (~300 lines total)\n- **Maintainability**: Automatic tracking of Nickel changes\n- **Compatibility**: Works with all Nickel versions\n- **User Expectations**: Same behavior as CLI users experience\n- **Community Alignment**: Uses official Nickel distribution\n\n### Negative\n\n- **External Dependency**: Requires `nickel` binary installed in PATH\n- **Process Overhead**: ~100-200 ms per execution (heavily cached)\n- **Subprocess Management**: Spawn handling and stderr capture needed\n- **Distribution**: Provisioning must include Nickel binary\n\n### Mitigation Strategies\n\n**Dependency Management**:\n\n- Installation scripts handle Nickel setup\n- Docker images pre-install Nickel\n- Clear error messages if `nickel` not found\n- Documentation covers installation\n\n**Performance**:\n\n- Aggressive caching (80-90% typical hit rate)\n- Cache hits: ~1-5 ms (not 100-200 ms)\n- Cache directory: `~/.cache/provisioning/config-cache/`\n\n**Distribution**:\n\n- Provisioning distributions include Nickel\n- Installers set up Nickel automatically\n- CI/CD has Nickel available\n\n## Alternatives Considered\n\n### Alternative 1: Pure Rust with nickel-lang-core\n\n**Pros**: No external dependency\n**Cons**: Undocumented API, high risk, maintenance burden\n**Decision**: REJECTED - Too risky\n\n### Alternative 2: Hybrid (Pure Rust + CLI fallback)\n\n**Pros**: Flexibility\n**Cons**: Adds complexity, dual code paths, confusing behavior\n**Decision**: REJECTED - Over-engineering\n\n### Alternative 3: WebAssembly Version\n\n**Pros**: Standalone\n**Cons**: WASM support unclear, additional infrastructure\n**Decision**: REJECTED - Immature\n\n### Alternative 4: Use Nickel LSP\n\n**Pros**: Uses official interface\n**Cons**: LSP not designed for evaluation, wrong abstraction\n**Decision**: REJECTED - Inappropriate tool\n\n## Implementation Details\n\n### Command Set\n\n1. **nickel-export**: Export/evaluate Nickel file\n\n ```nushell\n nickel-export json /path/to/file.ncl\n nickel-export yaml /path/to/file.ncl\n ```\n\n2. **nickel-eval**: Evaluate with automatic caching (for config loader)\n\n ```nushell\n nickel-eval /workspace/config.ncl\n ```\n\n3. **nickel-format**: Format Nickel files\n\n ```nushell\n nickel-format /path/to/file.ncl\n ```\n\n4. **nickel-validate**: Validate Nickel files/project\n\n ```nushell\n nickel-validate /path/to/project\n ```\n\n### Critical Implementation Detail: Command Syntax\n\nThe plugin uses the **correct Nickel command syntax**:\n\n```\n// Correct:\ncmd.arg("export").arg(file).arg("--format").arg(format);\n// Results in: "nickel export /file --format json"\n\n// WRONG (previously):\ncmd.arg("export").arg(format).arg(file);\n// Results in: "nickel export json /file"\n// ↑ This triggers auto-import of nonexistent JSON module\n```\n\n### Caching Strategy\n\n**Cache Key**: SHA256(file_content + format)\n**Cache Hit Rate**: 80-90% (typical provisioning workflows)\n**Performance**:\n\n- Cache miss: ~100-200 ms (process fork)\n- Cache hit: ~1-5 ms (filesystem read + parse)\n- Speedup: 50-100x for cached runs\n\n**Storage**: `~/.cache/provisioning/config-cache/`\n\n### JSON Output Processing\n\nPlugin correctly processes JSON output:\n\n1. Invokes: `nickel export /file.ncl --format json`\n2. Receives: JSON string from stdout\n3. Parses: serde_json::Value\n4. Converts: `json_value_to_nu_value()` (recursive)\n5. Returns: nu_protocol::Value::Record (not string!)\n\nThis enables Nushell cell path access:\n\n```\nnickel-export json /config.ncl | .database.host # ✅ Works\n```\n\n## Testing Strategy\n\n**Unit Tests**:\n\n- JSON parsing correctness\n- Value type conversions\n- Cache logic\n\n**Integration Tests**:\n\n- Real Nickel file execution\n- Module imports verification\n- Search path resolution\n\n**Manual Verification**:\n\n```\n# Test module imports\nnickel-export json /workspace/config.ncl\n\n# Test cell path access\nnickel-export json /workspace/config.ncl | .database\n\n# Verify output types\nnickel-export json /workspace/config.ncl | type\n# Should show: record, not string\n```\n\n## Configuration Integration\n\nPlugin integrates with provisioning config system:\n\n- Nickel path auto-detected: `which nickel`\n- Cache location: platform-specific `cache_dir()`\n- Errors: consistent with provisioning patterns\n\n## References\n\n- ADR-012: Nushell Plugins (general framework)\n- [Nickel Official Documentation](https://nickel-lang.org/)\n- [nickel-lang-core Rust Crate](https://crates.io/crates/nickel-lang-core/)\n- nu_plugin_nickel Implementation: `provisioning/core/plugins/nushell-plugins/nu_plugin_nickel/`\n- [Related: ADR-013-NUSHELL-KCL-PLUGIN](adr/adr-nushell-kcl-plugin-cli-wrapper.md)\n\n---\n\n**Status**: Accepted and Implemented\n**Last Updated**: 2025-12-15\n**Implementation**: Complete\n**Tests**: Passing