chore: update all plugins to Nushell 0.111.0
Some checks failed
Build and Test / Validate Setup (push) Has been cancelled
Build and Test / Build (darwin-amd64) (push) Has been cancelled
Build and Test / Build (darwin-arm64) (push) Has been cancelled
Build and Test / Build (linux-amd64) (push) Has been cancelled
Build and Test / Build (windows-amd64) (push) Has been cancelled
Build and Test / Build (linux-arm64) (push) Has been cancelled
Build and Test / Security Audit (push) Has been cancelled
Build and Test / Package Results (push) Has been cancelled
Build and Test / Quality Gate (push) Has been cancelled
Nightly Build / Check for Changes (push) Has been cancelled
Nightly Build / Validate Setup (push) Has been cancelled
Nightly Build / Nightly Build (darwin-amd64) (push) Has been cancelled
Nightly Build / Nightly Build (darwin-arm64) (push) Has been cancelled
Nightly Build / Nightly Build (linux-amd64) (push) Has been cancelled
Nightly Build / Nightly Build (windows-amd64) (push) Has been cancelled
Nightly Build / Nightly Build (linux-arm64) (push) Has been cancelled
Nightly Build / Create Nightly Pre-release (push) Has been cancelled
Nightly Build / Notify Build Status (push) Has been cancelled
Nightly Build / Nightly Maintenance (push) Has been cancelled

- Bump all 18 plugins from 0.110.0 to 0.111.0
  - Update rust-toolchain.toml channel to 1.93.1 (nu 0.111.0 requires ≥1.91.1)

  Fixes:
  - interprocess pin =2.2.x → ^2.3.1 in nu_plugin_mcp, nu_plugin_nats, nu_plugin_typedialog
    (required by nu-plugin-core 0.111.0)
  - nu_plugin_typedialog: BackendType::Web initializer — add open_browser: false field
  - nu_plugin_auth: implement missing user_info_to_value helper referenced in tests

  Scripts:
  - update_all_plugins.nu: fix [package].version update on minor bumps; add [dev-dependencies]
    pass; add nu-plugin-test-support to managed crates
  - download_nushell.nu: rustup override unset before rm -rf on nushell dir replace;
    fix unclosed ) in string interpolation
This commit is contained in:
Jesús Pérez 2026-03-11 03:22:42 +00:00
parent b6eeaee4da
commit d9ef2f0d5b
201 changed files with 29374 additions and 15475 deletions

View File

@ -1,10 +1,99 @@
# Changelog
## [0.111.0] - 2026-03-11 (NUSHELL 0.111.0 COMPATIBILITY)
### 🔧 Version Update
- Nushell 0.111.0 compatibility — all 18 plugins updated
- `rust-toolchain.toml` channel updated to `1.93.1` (nushell 0.111.0 requires rustc ≥1.91.1)
- Removed stale `rustup override` for `nushell/` directory from update toolchain
### 🐛 Fixes
- `interprocess` pin updated `=2.2.x``^2.3.1` in `nu_plugin_mcp`, `nu_plugin_nats`,
`nu_plugin_typedialog` — required by `nu-plugin-core 0.111.0`
- `nu_plugin_typedialog`: `BackendType::Web` initializer updated with new `open_browser: false`
field added in `typedialog-core`
- `nu_plugin_auth`: implemented missing `user_info_to_value` helper referenced in tests
- `download_nushell.nu`: fixed unclosed interpolation delimiter in version mismatch warning
- `update_all_plugins.nu`: fixed version update to also cover `[package].version` on minor bumps
and `[dev-dependencies]` section (`nu-plugin-test-support`); added `nu-plugin-test-support`
to managed crates list
- `download_nushell.nu`: `rustup override unset` before `rm -rf` on nushell directory replace —
prevents stale toolchain overrides surviving source replacement
---
## [0.110.0] - 2026-02-10 (TYPEDIALOG + MCP CLIENT PLUGINS)
### ✨ New Plugins
#### nu_plugin_typedialog
- Replaces `provisioning/core/shlib/` bash TTY wrappers with a native Nushell plugin
- `typedialog form <path> [--backend cli|web] [--port] [--initial <record>]` — execute
interactive form from TOML definition, returns record of results
- `typedialog nickel-roundtrip <ncl> <toml> <ncl> [--no-validate]` — read Nickel → form
→ write back, returns `{output_nickel, validation_passed, form_results, changed}`
- `typedialog text/confirm/select/multi-select/password` — direct prompt primitives
- ESC/cancel returns `null` (Value::nothing), never an error
- Backed by `typedialog-core` crate with CLI and Web backends
- Fixed `interprocess` API break via `=2.2.2` pin (nushell 0.110.0 compat)
#### nu_plugin_mcp
- MCP client plugin — spawns `provisioning-mcp-server` as child process
- Session state held in `Arc<Mutex<Option<McpSession>>>` on plugin struct (persists across
command calls within plugin process lifetime)
- `mcp connect <binary> [--provisioning-path]` — MCP handshake (initialize + initialized)
- `mcp tools list` — returns table `{name, description, required_args}`
- `mcp tool call <name> [--payload <record>]` — dispatches tool, maps content envelope
to Nu values (JSON-parsed text or `{error: true, message: ...}` record)
- `mcp disconnect` — kills child process, clears session
### 🔧 Infrastructure
- `provisioning-mcp-server/src/simple_main.rs` — raw JSON-RPC 2.0 stdio transport
replaces broken `rust-mcp-sdk` dependency; 37 tools restored from `main.rs.disabled`
- `plugin_registry.toml` — added entries for `nu_plugin_typedialog` and `nu_plugin_mcp`
---
## [0.109.0] - 2025-12-15 (KCL & NICKEL PLUGINS WITH CACHING)
### ✨ New Features
#### Configuration Loading Plugins with Caching
- **nu_plugin_kcl** - Enhanced with automatic caching support
- Added `kcl-eval` command for primary config loading with cache
- Added `kcl-cache-status` command for cache diagnostics
- SHA256-based cache keys: `~/.cache/provisioning/config-cache/`
- Expected performance: 20-50x improvement (cache hit ~5ms vs CLI ~300ms)
- **nu_plugin_nickel** - New independent plugin for Nickel configs
- Complete implementation with identical caching architecture
- Added `nickel-eval` command for primary config loading with cache
- Added `nickel-cache-status` command for cache diagnostics
- Shared cache directory with KCL plugin
- Commands: `nickel-eval`, `nickel-export`, `nickel-format`, `nickel-validate`, `nickel-cache-status`
### 🔧 Plugin Registry Enhancements
- Restructured registry format: `[plugins.*]` nested structure
- Added `local_path` field for all plugins
- Added `upstream_branch` field for upstream tracking
- Added `[settings]` section for managed dependencies
- Updated nu_plugin_nickel registry entry with complete metadata
---
## [0.109.0] - 2025-12-11 (COMPREHENSIVE DOCUMENTATION & COMMIT PREPARATION)
### 📚 Documentation Updates
#### Repository Documentation
- **CHANGES.md** (provisioning/core): Complete summary of core system updates
- CLI, libraries, plugins, and utilities changes
- File-by-file breakdown organized by directory
@ -16,6 +105,7 @@
- Ready for: `git commit -F provisioning/core/COMMIT_MESSAGE.md`
#### Repository Documentation (provisioning/)
- **CHANGES.md**: Summary of configuration and documentation updates
- Configuration files (config/, kcl/, core/, extensions/, platform/)
- Documentation updates across all modules
@ -41,6 +131,7 @@ All repositories now have:
### 🎯 Plugin Exclusion System (2025-12-03)
#### Architecture Implementation
- **Configuration-Driven Plugin Exclusion**:
- Central registry in `etc/plugin_registry.toml` for managing exclusions
- Single source of truth for which plugins excluded from distributions
@ -48,23 +139,27 @@ All repositories now have:
- Future-proof design supporting profiles and conditional exclusions
#### Collection System Enhancement (`scripts/collect_full_binaries.nu`)
- Added `get_excluded_plugins()` helper function to load exclusion list
- Updated `get_workspace_plugins_info()` to filter excluded workspace plugins
- Updated `get_custom_plugins_info()` to filter excluded custom plugins
- Distribution collections now exclude specified plugins automatically
#### Packaging System Enhancement (`scripts/create_distribution_packages.nu`)
- Added `get_excluded_plugins_dist()` helper function
- Updated `get_plugin_components()` to filter both custom and workspace excluded plugins
- Distribution packages now exclude specified plugins automatically
- Consistent filtering with collection system
#### Installation Configuration (`scripts/templates/default_config.nu`)
- Removed excluded plugins from auto-load plugin list
- Added documentation explaining why plugins are excluded
- Users won't see missing plugin errors in fresh installations
#### Documentation - Complete Coverage
- **User Guide**: `docs/PLUGIN_EXCLUSION_GUIDE.md` (400+ lines)
- Quick start for users, developers, release managers
- Common tasks with step-by-step instructions
@ -107,6 +202,7 @@ All repositories now have:
- Testing validation
#### Configuration Updates
- **Registry**: `etc/plugin_registry.toml`
- Added `[distribution]` section with `excluded_plugins` list
- Marked `nu_plugin_example` as excluded with reason documentation
@ -114,12 +210,14 @@ All repositories now have:
#### Files Modified (9 files total)
**Implementation** (4 files):
- `etc/plugin_registry.toml` - Config: Added `[distribution]` section
- `scripts/collect_full_binaries.nu` - Feature: Added filtering functions
- `scripts/create_distribution_packages.nu` - Feature: Added filtering functions
- `scripts/templates/default_config.nu` - Config: Removed excluded from auto-load
**Documentation** (5 files):
- `docs/PLUGIN_EXCLUSION_GUIDE.md` - NEW: User guide
- `docs/architecture/README.md` - NEW: Navigation index
- `docs/architecture/PLUGIN_EXCLUSION_SYSTEM.md` - NEW: Technical spec
@ -128,6 +226,7 @@ All repositories now have:
- `docs/PROVISIONING_PLUGINS_SUMMARY.md` - UPDATED: Added links
#### Impact & Behavior Changes
- ✅ Build system UNCHANGED - all plugins still built
- ✅ Test system UNCHANGED - all plugins still tested
- ✅ Dev workflows UNCHANGED - developers can use excluded plugins
@ -136,6 +235,7 @@ All repositories now have:
- ❌ Auto-load NOW excludes specified plugins from user configs
#### Example: nu_plugin_example
- ✅ Still built with `just build`
- ✅ Still tested with `just test`
- ✅ Still available in build output for reference
@ -144,6 +244,7 @@ All repositories now have:
- ❌ NOT auto-loaded in user installations
#### Testing & Verification
- ✅ Registry parses correctly
- ✅ Collection system excludes plugins
- ✅ Packaging system excludes plugins
@ -154,6 +255,7 @@ All repositories now have:
- ✅ All 1,400+ lines of documentation complete
#### Quality Metrics
- **Code Changes**: 40 lines added, 1 line removed (net +39)
- **Documentation**: 1,400+ lines of comprehensive coverage
- **Error Handling**: 100% graceful degradation
@ -168,6 +270,7 @@ All repositories now have:
### 🚀 Bootstrap Installer & Distribution Improvements (2025-10-19)
#### Install Script Architecture (DRY Design)
- **Implemented Symlink-Based DRY Architecture**:
- Single source of truth: `installers/bootstrap/install.sh` (1,247 lines)
- Symlinks: `./install.sh``installers/bootstrap/install.sh`
@ -176,6 +279,7 @@ All repositories now have:
- No code duplication across installation paths
#### Archive Extraction Fixes (Critical)
- **Fixed Archive Binary Detection (Version-Agnostic)**:
- Root cause: `find` command returning parent directory itself in results
- Solution: Added `-not -path "$extract_dir"` to exclude starting directory
@ -191,6 +295,7 @@ All repositories now have:
- Validates binaries exist before using them
#### Plugin Registration Error Handling
- **Improved Plugin Registration with Version Mismatch Detection**:
- Captures both stdout and stderr from plugin add commands
- Detects version incompatibility errors: "is not compatible with version"
@ -202,6 +307,7 @@ All repositories now have:
- Installation continues successfully even with version mismatches
#### Shell Configuration PATH Update Messaging
- **Fixed Confusing PATH Update Messages**:
- Root cause: Script conflated "PATH found" with "PATH needs updating"
- Solution: Track two separate states:
@ -213,6 +319,7 @@ All repositories now have:
- ⚠️ "Could not find..." (when file doesn't exist)
#### Installation Features
- **`--source-path` Option for Local Installation**:
- Install from local archive: `--source-path archive.tar.gz`
- Install from local directory: `--source-path /path/to/binaries`
@ -226,6 +333,7 @@ All repositories now have:
- Preserves user choice (keep or remove config)
#### Documentation Updates
- **Updated CLAUDE.md** with:
- Install Script Architecture (DRY Design) section
- Source of truth location and symlink structure
@ -241,6 +349,7 @@ All repositories now have:
- How DRY Works (3-step explanation)
#### Files Modified
- `installers/bootstrap/install.sh` - All fixes (1,247 lines, +3 lines)
- `./install.sh` - Auto-updated via symlink
- `./scripts/templates/install.sh` - Auto-updated via symlink
@ -248,6 +357,7 @@ All repositories now have:
- `README.md` - Added install script section
#### Testing & Verification
- ✅ Archive extraction works with version-agnostic detection
- ✅ Installation to `~/.local` successful (16 binaries)
- ✅ Installation to `~/.local/bin` successful (21 plugins loaded)
@ -256,6 +366,7 @@ All repositories now have:
- ✅ Clean uninstall followed by fresh reinstall works perfectly
#### Impact
- ✅ Users can install from any version of nushell-full archive
- ✅ Clear error messages if binaries not found in archive
- ✅ Version mismatch plugins skipped without breaking installation
@ -270,6 +381,7 @@ All repositories now have:
### 🎯 Help System & Build Process Fixes (2025-10-19)
#### Help System Improvements
- **Added Version Update Module to Help System**:
- Version-update module now discoverable via `just help modules`
- Added to `just help` main output with key commands
@ -277,11 +389,13 @@ All repositories now have:
- Full integration into help system navigation
#### New Help Commands
- **`just commands`**: New recipe showing all commands organized by group (alias for `just --list`)
- Shows [version-update] group with all 30+ update recipes
- Replaces need to manually run `just --list`
#### Build Process Fixes
- **Fixed Plugin Archive Creation Bug**:
- Phase 3 "No plugins found" warning fixed
- Root cause: `each` command returns null, breaking count logic
@ -301,6 +415,7 @@ All repositories now have:
- Properly calculates archive file size in MB
#### Plugin Dependency Update Optimization
- **Fixed Unnecessary Plugin Rebuilds**:
- Root cause: `update_all_plugins.nu` always touched all Cargo.toml files
- Solution: Only save if content actually changed
@ -309,6 +424,7 @@ All repositories now have:
- Result: Only plugins with real changes trigger rebuilds (hashes, highlight)
#### Files Modified
- `justfiles/help.just` - Added version-update module to help system
- `scripts/create_full_distribution.nu` - Fixed plugin collection filtering (exclude .d files)
- `scripts/create_distribution_packages.nu` - Fixed get_plugin_components to look in correct directories
@ -316,6 +432,7 @@ All repositories now have:
- `CHANGELOG.md` - Documented all fixes
#### Archive Content & Structure Fixes (Critical Fix)
- **Fixed nushell-full archive missing plugins**:
- Root cause: `get_plugin_components` looked in distribution directory (staging output)
- Solution: Look in `nu_plugin_*/target/release/` (actual binaries)
@ -343,6 +460,7 @@ All repositories now have:
- Reduces archive size and keeps distribution focused
#### Impact
- ✅ Version-update commands now fully discoverable
- ✅ Phase 3 bin archive creation works correctly
- ✅ nushell-full archive contains nu + all plugins
@ -357,23 +475,27 @@ All repositories now have:
### 🎯 Nushell Core Update: 0.107.1 → 0.108.0
#### Major Changes
- **Updated Nushell to 0.108.0** with MCP (Model Context Protocol) support
- **Fixed critical documentation bugs** in `best_nushell_code.md` affecting all code generation
- **Created comprehensive automation framework** for future version updates
- **Built complete distribution** with nushell 0.108.0 + all plugins
#### Critical Bug Fixes
- **Rule 16 (Function Signatures)**: Fixed incorrect syntax `]: type {``]: nothing -> type {`
- **Rule 17 (String Interpolation)**: Fixed non-working syntax `[$var]``($var)`
- Both bugs were in documentation and caused all generated code to fail parsing
#### New Features
- ✅ **MCP Support**: Model Context Protocol for AI agent integration
- ✅ **Enhanced SQLite**: Improved database operations
- ✅ **System Clipboard**: Native clipboard integration
- ✅ **Trash Support**: Safe file deletion to trash
#### Breaking Changes
- **`into value``detect type`**: Command deprecated (still works with warning)
- Shows helpful migration message
- Recommends `update cells {detect type}` instead
@ -382,6 +504,7 @@ All repositories now have:
- Requires explicit error handling with `try`/`catch`
#### New Automation Scripts (8 scripts created)
1. **`download_nushell.nu`** (285 lines) - Download from GitHub tags
2. **`analyze_nushell_features.nu`** (350 lines) - Parse and validate features
3. **`audit_crate_dependencies.nu`** (390 lines) - Audit plugin dependencies
@ -392,18 +515,21 @@ All repositories now have:
8. **`complete_update.nu`** (NEW) - All-in-one update script
#### Documentation Created
- **`updates/108/NUSHELL_0.108_UPDATE_SUMMARY.md`** - Complete update summary
- **`updates/108/MIGRATION_0.108.0.md`** - Step-by-step migration guide
- **`updates/108/NUSHELL_UPDATE_AUTOMATION.md`** - Automation documentation
- **`guides/COMPLETE_VERSION_UPDATE_GUIDE.md`** - Comprehensive update guide
#### Build System Improvements
- **Build Time**: Optimized to 2m 55s (from 15+ minutes)
- **Features**: All desired features validated and included
- **Workspace**: Proper `--workspace` flag for system plugins
- **Artifacts**: Complete binary collection system
#### Validation & Testing
- ✅ All syntax patterns tested against actual 0.108.0 binary
- ✅ Function signatures validated
- ✅ String interpolation validated
@ -412,12 +538,14 @@ All repositories now have:
- ✅ Breaking changes verified
#### Impact
- **Developer Experience**: 80% reduction in update time with automation
- **Code Quality**: All future code will use correct syntax
- **Maintainability**: Semi-automated updates with 3 manual checkpoints
- **Documentation**: Comprehensive guides for all future updates
#### Files Modified
- `best_nushell_code.md` - Fixed Rules 16 & 17, Quick Reference, Summary
- `nushell/` - Updated to 0.108.0
- `nu_plugin_*/Cargo.toml` - Dependency versions updated
@ -426,6 +554,7 @@ All repositories now have:
- `guides/` - New comprehensive guide
#### Migration Notes
- Old `into value` usage still works but shows deprecation warning
- Update to `detect type` or `update cells {detect type}` to remove warnings
- Stream collection operations may need `try`/`catch` for error handling
@ -438,6 +567,7 @@ All repositories now have:
### 🚀 Major Feature: Complete Nushell Distribution System
#### Full Distribution Infrastructure
- **Complete Nushell binary distribution**: Added capability to build, package, and distribute Nushell itself alongside plugins
- **Zero-prerequisite installation**: Users can install complete Nushell environment without having Rust, Cargo, or Nushell pre-installed
- **Cross-platform bootstrap installers**: Universal POSIX shell installer (`install.sh`) and Windows PowerShell installer (`install.ps1`)
@ -445,6 +575,7 @@ All repositories now have:
- **Self-contained distribution packages**: Complete packages including binaries, configuration, documentation, and installers
#### New Build System
- **`scripts/build_nushell.nu`**: Comprehensive script to build nushell with all workspace plugins
- **`scripts/collect_full_binaries.nu`**: Advanced binary collection system for nushell + all plugins
- **`scripts/create_distribution_packages.nu`**: Multi-platform package creator with manifest generation
@ -452,6 +583,7 @@ All repositories now have:
- **Enhanced justfile**: Added 40+ new recipes in `justfiles/full_distro.just` for complete distribution workflows
#### Installation and Configuration System
- **`scripts/install_full_nushell.nu`**: Advanced nu-based installer with plugin selection and configuration options
- **`scripts/verify_installation.nu`**: Comprehensive installation verification with detailed reporting
- **`scripts/templates/default_config.nu`**: Complete 500+ line Nushell configuration with optimizations
@ -459,6 +591,7 @@ All repositories now have:
- **`etc/distribution_config.toml`**: Central distribution configuration management
#### Bootstrap Installers (Zero Prerequisites)
- **`installers/bootstrap/install.sh`**: 900+ line universal POSIX installer for Linux/macOS
- Automatic platform detection and binary download
- Build from source fallback capability
@ -468,12 +601,15 @@ All repositories now have:
- **Complete documentation**: Installation guides, troubleshooting, and security considerations
#### Uninstall System
- **`scripts/templates/uninstall.sh`** and **`uninstall.ps1`**: Clean removal scripts for all platforms
- **Complete cleanup**: Removes binaries, configurations, PATH entries with optional backup
- **Plugin unregistration**: Clean removal from nushell registry
#### Key Distribution Workflows
```bash
```nushell
bash
# Build complete distribution
just build-full # Build nushell + all plugins
@ -489,6 +625,7 @@ just release-full-cross # Full cross-platform release
```
#### Installation Experience
- **One-liner installation**: `curl -sSf https://your-url/install.sh | sh`
- **Multiple installation modes**: User (~/.local/bin), system (/usr/local/bin), portable
- **Automatic plugin registration**: All plugins registered and verified with `nu -c "plugin list"`
@ -497,6 +634,7 @@ just release-full-cross # Full cross-platform release
### 🎯 Major Updates
#### Documentation and Repository Structure
- **Enhanced README.md**: Significantly expanded documentation with 682 new lines covering:
- Comprehensive plugin collection overview
- Detailed development workflows and automation
@ -505,12 +643,14 @@ just release-full-cross # Full cross-platform release
- Complete command reference and usage examples
#### Script and Automation Cleanup
- **Removed legacy scripts**: Cleaned up old bash scripts (build-all.sh, collect-install.sh, make_plugin.sh)
- **Streamlined automation**: Consolidated script system in favor of unified approach via justfile and nushell scripts
### 🔧 Plugin Updates and Dependency Management
#### Nushell Core Updates
- **Updated nushell submodule**: Comprehensive update to latest nushell version (0.107.1)
- **Synchronized dependencies**: Updated all nu-* dependencies across all plugins for version consistency
- **Updated Cargo.lock files**: Refreshed dependency lock files for all plugins
@ -518,25 +658,30 @@ just release-full-cross # Full cross-platform release
#### Plugin-Specific Changes
##### nu_plugin_clipboard
- Updated Cargo.toml with new dependency versions
- Refreshed Cargo.lock with 253 dependency changes
##### nu_plugin_desktop_notifications
- Updated Cargo.toml for nushell 0.107.1 compatibility
- Refreshed Cargo.lock with 218 dependency updates
##### nu_plugin_hashes
- **Enhanced functionality**: Updated hasher.rs implementation
- **Build system improvements**: Modified build.rs configuration
- Updated Cargo.toml with 24 configuration changes
- Refreshed Cargo.lock with 283 dependency updates
##### nu_plugin_highlight
- **Code improvements**: Enhanced highlight.rs and plugin.rs implementations
- Updated for new nushell plugin API compatibility
- Refreshed Cargo.lock with 476 dependency updates
##### nu_plugin_image
- **Major code refactoring**: Comprehensive updates to image processing modules
- **Removed deprecated code**: Deleted ansi_to_image.rs (replaced with modular approach)
- **Enhanced modules**:
@ -548,21 +693,25 @@ just release-full-cross # Full cross-platform release
- Refreshed Cargo.lock with 494 dependency updates
##### nu_plugin_kcl and nu_plugin_tera
- Updated submodule references
- Synchronized with latest upstream changes
##### nu_plugin_port_extension and nu_plugin_qr_maker
- Updated Cargo.toml for version consistency
- Refreshed Cargo.lock files
#### API KCL Plugin
- Updated Cargo.lock with 266 dependency changes
### 🏗️ Repository Infrastructure Updates
#### Git Tracking Cleanup
- **Removed nushell directory from tracking**: The nushell submodule directory is now properly ignored
- **Updated .gitignore**: Added patterns for nushell directory, nushell-* files, and *.tar.gz archives
- **Updated .gitignore**: Added patterns for nushell directory, nushell-*files, and*.tar.gz archives
### 📊 Statistics Summary

View File

@ -1,20 +1,19 @@
# 🚀 Nushell Plugins Repository
**Current Nushell Version**: 0.108.0 | **Last Updated**: 2025-10-18
**Current Nushell Version**: 0.111.0 | **Last Updated**: 2026-03-11
A comprehensive collection of nushell plugins with automated upstream tracking, dependency management, development workflows, **complete Nushell distribution system**, and **semi-automated version update framework**.
## 🎯 Latest Update: Nushell 0.108.0
## 🎯 Latest Update: Nushell 0.111.0
**Major highlights of the 0.108.0 update:**
- ✅ **MCP Support**: Model Context Protocol for AI agent integration
- ✅ **Critical Bug Fixes**: Fixed documentation syntax errors affecting all code generation
- ✅ **Automation Framework**: 8 new scripts for semi-automated version updates
- ✅ **Complete Documentation**: Migration guides, automation docs, and validation reports
- ✅ **80% Faster Updates**: Automated workflows with strategic manual checkpoints
**Highlights of the 0.111.0 update:**
**→ [START HERE: UPDATE.md](UPDATE.md)** for version update instructions
See [`CHANGELOG.md`](CHANGELOG.md) for complete details | Read [`updates/108/`](updates/108/) for full documentation
- ✅ **18 plugins updated** to Nushell 0.111.0
- ✅ **Rust toolchain** bumped to 1.93.1 (nushell 0.111.0 requires ≥1.91.1)
- ✅ **interprocess ^2.3.1** — resolved conflict with nu-plugin-core 0.111.0 across mcp/nats/typedialog
- ✅ **Update script fixes**`[package].version` and `[dev-dependencies]` now correctly updated on minor version bumps
See [`CHANGELOG.md`](CHANGELOG.md) for complete details.
## 🆕 NEW: Full Nushell Distribution System
@ -23,6 +22,7 @@ See [`CHANGELOG.md`](CHANGELOG.md) for complete details | Read [`updates/108/`](
This repository provides **complete Nushell distributions** that include Nushell itself plus all plugins, offering zero-prerequisite installation for end users:
### 🎯 End User Installation (Zero Prerequisites)
```bash
# One-liner installation (Linux/macOS)
curl -sSf https://your-url/install.sh | sh
@ -36,6 +36,7 @@ tar -xzf nushell-full-*.tar.gz && cd nushell-full-* && ./install.sh
```
### 🚀 Developer Distribution Commands
```bash
just build-full # Build nushell + all plugins
just pack-full # Create distribution package
@ -45,6 +46,7 @@ just release-full-cross # Complete release workflow
```
### ✨ Key Features
- **Zero Prerequisites**: No Rust, Cargo, or Nu installation required
- **Cross-Platform**: Linux, macOS, Windows support
- **Complete Environment**: Nushell + all plugins + configuration
@ -83,6 +85,7 @@ tar -xzf nushell-full-*.tar.gz && cd nushell-full-* && ./install.sh
### For Developers
#### Prerequisites
```bash
# Install required tools
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Rust
@ -91,6 +94,7 @@ cargo install just # Just (optional
```
#### Development Workflow
```bash
# Clone the repository
git clone <repository-url>
@ -130,6 +134,7 @@ The Full Distribution System transforms this repository from a **development-foc
### End User Experience
**Before**: Complex development setup required
```bash
# Users needed to:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # Install Rust
@ -139,6 +144,7 @@ just validate-nushell && just build # Build everythin
```
**After**: Zero-prerequisite installation
```bash
# Users now only need:
curl -sSf https://your-url/install.sh | sh # Done!
@ -149,6 +155,7 @@ curl -sSf https://your-url/install.sh | sh # Done!
#### Building Complete Distributions
**Step 1: Build Nushell + System Plugins**
```bash
# Build Nushell with built-in system plugins (uses cargo build --workspace)
just build-nushell # Builds: nu + nu_plugin_formats, nu_plugin_inc, nu_plugin_gstat,
@ -159,6 +166,7 @@ just build-nushell-target linux-arm64 # Cross-compile system plugins
```
**Step 2: Build Everything (System + Custom Plugins)**
```bash
# Build nushell + system plugins + custom plugins from this repo
just build-full # Native build (calls build-nushell + build custom plugins)
@ -167,6 +175,7 @@ just build-full-all # All platforms
```
**Step 3: Collect Built Binaries**
```bash
# Collect nushell binary + all plugins for distribution
just collect # Current platform (darwin-arm64)
@ -175,6 +184,7 @@ just collect-platform PLATFORM # Specific platform
```
**Step 4: Create Distribution Packages**
```bash
# Create distribution packages
just pack-full # Current platform
@ -187,6 +197,7 @@ just test-install-full # Test complete installation process
```
#### Cross-Platform Release
```bash
# Complete release workflow
just release-full-cross # Build → Pack → Verify for all platforms
@ -200,6 +211,7 @@ just release-full-windows # Windows distributions
#### Installation Modes
**System Installation** (recommended for end users):
```bash
# Install to system paths with proper integration
./install.sh
@ -212,6 +224,7 @@ just release-full-windows # Windows distributions
```
**Local Installation** (development/testing):
```bash
# Install to user directory without system integration
./install.sh --local
@ -223,6 +236,7 @@ just release-full-windows # Windows distributions
### Distribution Architecture
#### Components Included
- **Nushell Binary**: Complete nushell installation
- **All Plugins**: Every plugin in the repository
- **Configuration**: Optimized default configuration
@ -231,6 +245,7 @@ just release-full-windows # Windows distributions
- **Verification**: Installation integrity checks
#### Platform Support
| Platform | Architecture | Status | Installation Method |
|----------|-------------|--------|-------------------|
| **Linux** | x86_64 | ✅ Full | `curl` installer + manual |
@ -240,6 +255,7 @@ just release-full-windows # Windows distributions
| **Windows** | x86_64 | ✅ Full | PowerShell installer + manual |
#### Bootstrap Installers
- **Smart Detection**: Automatically detects platform and architecture
- **Conflict Resolution**: Handles existing Nushell installations
- **Path Management**: Configures PATH and shell integration
@ -249,18 +265,21 @@ just release-full-windows # Windows distributions
### Use Cases
#### For End Users
- **Simple Installation**: Get Nushell + all plugins with one command
- **No Prerequisites**: No need for Rust, Git, or development tools
- **Professional Experience**: Clean installation/uninstallation
- **Immediate Productivity**: Pre-configured environment with all plugins
#### For System Administrators
- **Bulk Deployment**: Deploy to multiple systems easily
- **Consistent Environment**: Identical setup across all machines
- **Offline Installation**: Manual packages work without internet
- **Integration Ready**: System-wide installation with proper paths
#### for Distributors/Packagers
- **Ready Binaries**: All binaries built and tested
- **Cross-Platform**: Support for all major platforms
- **Checksums Included**: Integrity verification built-in
@ -302,33 +321,34 @@ This repository provides:
| **nu_plugin_fluent** | ✅ Tracked | Upstream | Fluent localization framework |
| **nu_plugin_tera** | ✅ Tracked | Private | Tera templating engine (private repo) |
| **nu_plugin_kcl** | ✅ Tracked | Private | KCL configuration language (private repo) |
| **nu_plugin_nickel** | 🏠 Local | Local | Nickel configuration language (eval, export, format, validate) |
### Provisioning Platform Plugins (NEW)
### Provisioning Platform Plugins
High-performance native plugins for the provisioning platform with **10x performance improvement** over HTTP APIs:
High-performance native plugins for the provisioning platform:
| Plugin | Type | Description | Performance Gain |
|--------|------|-------------|------------------|
| **nu_plugin_auth** | 🔐 Security | JWT authentication, MFA (TOTP/WebAuthn), session management | 20% faster |
| **nu_plugin_kms** | 🔑 Security | Multi-backend KMS (RustyVault, Age, Cosmian, AWS, Vault) | **10x faster** |
| **nu_plugin_orchestrator** | 🎯 Operations | Orchestrator status, workflow validation, task management | **10x faster** |
**Key Features**:
- **Native Performance**: Direct Rust integration eliminates HTTP overhead
- **Pipeline Integration**: Full Nushell pipeline support
- **Multi-Backend KMS**: RustyVault, Age, Cosmian, AWS KMS, HashiCorp Vault
- **Security First**: JWT, MFA (TOTP/WebAuthn), keyring storage
- **Production Ready**: Comprehensive tests, error handling, documentation
| Plugin | Type | Description |
|--------|------|-------------|
| **nu_plugin_auth** | 🔐 Security | JWT authentication, MFA (TOTP/WebAuthn), session management |
| **nu_plugin_kms** | 🔑 Security | Multi-backend KMS (RustyVault, Age, Cosmian, AWS, Vault) |
| **nu_plugin_orchestrator** | 🎯 Operations | Orchestrator status, workflow validation, task management |
| **nu_plugin_typedialog** | 💬 UX | Interactive forms/prompts via TypeDialog — replaces shlib TTY wrappers |
| **nu_plugin_mcp** | 🤖 AI | MCP client — spawn and interact with `provisioning-mcp-server` |
**Quick Start**:
```bash
# Build and register provisioning plugins
just build-plugin nu_plugin_auth
just build-plugin nu_plugin_kms
just build-plugin nu_plugin_orchestrator
just build-plugin nu_plugin_typedialog
just build-plugin nu_plugin_mcp
just install-plugin nu_plugin_auth
just install-plugin nu_plugin_kms
just install-plugin nu_plugin_orchestrator
just install-plugin nu_plugin_typedialog
just install-plugin nu_plugin_mcp
# Verify installation
nu -c "plugin list | where name =~ provisioning"
@ -337,6 +357,7 @@ nu -c "plugin list | where name =~ provisioning"
**See Full Guide**: `docs/user/NUSHELL_PLUGINS_GUIDE.md`
### Legend
- ✅ **Tracked**: Has upstream repository with automated tracking
- 🏠 **Local**: Developed locally without upstream
- 🔒 **Private**: Private repository with tracking (requires authentication)
@ -412,6 +433,7 @@ For detailed instructions, platform-specific setup, troubleshooting, and advance
**➡️ [Complete Building and Cross-Compilation Guide](docs/BUILDING.md)**
The guide covers:
- Platform-specific setup and dependencies
- Native vs Docker cross-compilation
- Configuration and customization
@ -608,7 +630,7 @@ The repository now uses a **unified script system** with automatic nushell detec
### Directory Structure
```
```plaintext
scripts/
├── run.sh # Universal script runner with version checking
├── check_version.nu # Version consistency validator
@ -682,7 +704,7 @@ Every script automatically validates that your system nushell version matches th
## 📁 File Structure
```
```plaintext
nushell-plugins/
├── README.md # This file
├── justfile # Just task runner recipes
@ -731,6 +753,7 @@ nu --version && nu -c "plugin list"
```
**What you get:**
- Complete Nushell installation
- All plugins from this repository
- Optimized configuration
@ -742,6 +765,7 @@ nu --version && nu -c "plugin list"
The installation system uses a **single source of truth** with symlinks to eliminate code duplication:
**Key Files:**
- **Source of Truth**: `installers/bootstrap/install.sh` (1,176 lines)
- Complete bootstrap installer for Nushell + plugins
- Repository: `https://github.com/jesusperezlorenzo/nushell-plugins`
@ -775,6 +799,7 @@ Install from local archives or directories without downloading:
```
**Version-Agnostic Archive Detection**:
- Automatically detects any `nushell-*` version directory
- Works with: 0.107, 0.108, 0.109, or any future version
- Searches for binaries in: `bin/nu` (preferred) → root `nu` (fallback)
@ -782,6 +807,7 @@ Install from local archives or directories without downloading:
- Smart extraction and path detection
**Installation Options**:
```bash
./install.sh # Interactive mode
./install.sh --install-dir ~/.local # Custom path, non-interactive
@ -792,6 +818,7 @@ Install from local archives or directories without downloading:
```
**How DRY Works**:
1. **Single Source**: Only `installers/bootstrap/install.sh` is edited
2. **Symlinks Propagate**: All changes automatically available at `./install.sh` and `./scripts/templates/install.sh`
3. **Verified**: All symlinked paths contain identical content
@ -939,6 +966,7 @@ just validate # Validate setup and dependencies
**A:** This system requires your installed nushell version to match the submodule version for consistency. All operations automatically check version consistency and will fail if versions don't match.
**Solutions:**
- **Auto-fix**: `just fix-nushell` or `./scripts/run.sh --fix --check-only`
- **Manual check**: `just validate-nushell`
- **Skip (not recommended)**: `./scripts/run.sh --no-version-check script.nu`
@ -946,6 +974,7 @@ just validate # Validate setup and dependencies
### Q: What happened to the bash wrapper scripts?
**A:** The repository has been consolidated to eliminate script duplication. The old bash wrappers in `scripts/sh/` have been removed in favor of:
- **Universal wrapper**: `./scripts/run.sh` with automatic version checking
- **Direct nu scripts**: All scripts moved from `scripts/nu/` to `scripts/`
- **Just recipes**: Updated to use the new system
@ -953,6 +982,7 @@ just validate # Validate setup and dependencies
### Q: How does the new script system work?
**A:** The new system provides:
1. **Universal wrapper** (`scripts/run.sh`) with automatic nushell detection and version validation
2. **Consolidated scripts** - all nu scripts in `scripts/` directory
3. **Mandatory version checking** - every operation validates version consistency
@ -965,6 +995,7 @@ just validate # Validate setup and dependencies
### Q: What happens to my local changes during upstream merges?
**A:** Local changes are preserved. The system:
1. Creates backup branches before any merge
2. Applies upstream changes in a temporary branch
3. Restores your local nu_* dependency versions
@ -978,6 +1009,7 @@ just validate # Validate setup and dependencies
### Q: How do I add a new plugin to the repository?
**A:** Use the template generator:
```bash
just make-plugin nu_plugin_myfeature
# or
@ -987,6 +1019,7 @@ nu scripts/nu/make_plugin.nu nu_plugin_myfeature
### Q: What if upstream tracking fails?
**A:** Check the plugin status with `just status`. Failed plugins show error details. Common issues:
- Network connectivity to upstream repository
- Authentication for private repositories
- Merge conflicts requiring manual resolution
@ -994,6 +1027,7 @@ nu scripts/nu/make_plugin.nu nu_plugin_myfeature
### Q: How do I update nushell dependency versions?
**A:** Use the version updater:
```bash
just update-nu-versions # Update all plugins
just list-nu-versions # Show current versions
@ -1006,6 +1040,7 @@ just list-nu-versions # Show current versions
### Q: How do I distribute plugins to other systems?
**A:** Use the distribution pipeline:
```bash
just build # Build all plugins
just collect # Collect binaries
@ -1017,6 +1052,7 @@ The resulting archive contains all binaries and installation scripts.
### Q: What's the difference between shell scripts and nushell scripts?
**A:**
- **Nushell scripts** (`scripts/nu/`): Primary implementation with full features
- **Shell scripts** (`scripts/sh/`): Wrappers for compatibility with non-nushell environments
- Both provide the same functionality, use whichever fits your environment
@ -1024,6 +1060,7 @@ The resulting archive contains all binaries and installation scripts.
### Q: How do I contribute a new plugin?
**A:**
1. Create the plugin: `just make-plugin nu_plugin_yourname`
2. Implement your functionality
3. Add upstream URL to `etc/plugin_registry.toml` if it has one
@ -1033,12 +1070,14 @@ The resulting archive contains all binaries and installation scripts.
### Q: How do I exclude plugins from upstream tracking?
**A:** Edit `etc/upstream_exclude.toml` to exclude plugins from specific operations:
- Add to `[exclude]` section to exclude from all operations
- Add to `[exclude.check]` to skip automatic checks
- Add to `[exclude.merge]` to prevent automatic merging
- Use patterns like `nu_plugin_test_*` to exclude multiple plugins
You can also use Just commands:
```bash
just exclude nu_plugin_test add # Add to exclusion
just exclude nu_plugin_test remove # Remove from exclusion

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -182,12 +182,6 @@ additional_dirs = [
[packaging]
# Output directory for packages
output_dir = "bin_archives"
# Package formats per platform
formats = {
linux = ["tar.gz"],
macos = ["tar.gz"],
windows = ["zip"]
}
# Compression level (0-9)
compression_level = 6
# Whether to generate checksums
@ -195,6 +189,12 @@ generate_checksums = true
# Checksum algorithms
checksum_algorithms = ["sha256", "sha512"]
# Package formats per platform
[packaging.formats]
linux = ["tar.gz"]
macos = ["tar.gz"]
windows = ["zip"]
# Manifest settings
[packaging.manifest]
# Include detailed manifest in packages
@ -210,11 +210,6 @@ include_plugin_info = true
# Installation settings
[installation]
# Default configuration templates
config_templates = {
env = "scripts/templates/default_env.nu",
config = "scripts/templates/default_config.nu"
}
# Whether to create config directory
create_config_dir = true
# Whether to update PATH
@ -227,6 +222,11 @@ shell_scripts = [
"installers/bootstrap/install.ps1"
]
# Default configuration templates
[installation.config_templates]
env = "scripts/templates/default_env.nu"
config = "scripts/templates/default_config.nu"
# Verification settings
[verification]
# Enable installation verification
@ -274,11 +274,10 @@ generate_release_notes = true
# Pre-release validation
validate_before_release = true
# Release channels
channels = {
stable = "main",
beta = "develop",
nightly = "nightly"
}
[release.channels]
stable = "main"
beta = "develop"
nightly = "nightly"
# Documentation settings
[documentation]

View File

@ -1,10 +1,9 @@
# Nushell Plugin Registry for Provisioning Platform
# This file tracks available Nushell plugins with metadata
[nu_plugin_auth]
[plugins.nu_plugin_auth]
upstream_url = "local"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "nu_plugin_auth"
description = "Authentication plugin (JWT, MFA) for provisioning platform"
version = "0.1.0"
category = "provisioning"
@ -14,59 +13,71 @@ commands = [
"auth verify",
"auth sessions",
"auth mfa enroll",
"auth mfa verify"
"auth mfa verify",
]
dependencies = [
"jsonwebtoken",
"reqwest",
"keyring",
"rpassword",
"qrcode"
"qrcode",
]
[nu_plugin_kms]
[plugins.nu_plugin_kms]
upstream_url = "local"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "nu_plugin_kms"
description = "KMS plugin (RustyVault, Age, Cosmian) for provisioning platform"
version = "0.1.0"
category = "provisioning"
backends = ["rustyvault", "age", "cosmian", "aws", "vault"]
backends = [
"rustyvault",
"age",
"cosmian",
"aws",
"vault",
]
commands = [
"kms encrypt",
"kms decrypt",
"kms generate-key",
"kms status"
"kms status",
]
dependencies = [
"reqwest",
"age",
"base64",
"serde"
"serde",
]
[nu_plugin_orchestrator]
[plugins.nu_plugin_orchestrator]
upstream_url = "local"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "nu_plugin_orchestrator"
description = "Orchestrator operations plugin (status, validate, tasks)"
version = "0.1.0"
category = "provisioning"
commands = [
"orch status",
"orch validate",
"orch tasks"
"orch tasks",
]
dependencies = [
"serde_json",
"serde_yaml",
"walkdir"
"walkdir",
]
[nu_plugin_inquire]
[plugins.nu_plugin_inquire]
upstream_url = "https://github.com/jesusperezlorenzo/nu_plugin_inquire"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "nu_plugin_inquire"
description = "Interactive forms and prompts plugin using inquire crate - solves TTY buffering issues"
version = "0.1.0"
category = "utility"
@ -79,7 +90,7 @@ commands = [
"inquire custom",
"inquire editor",
"inquire date",
"inquire form"
"inquire form",
]
dependencies = [
"inquire",
@ -87,13 +98,15 @@ dependencies = [
"serde_json",
"toml",
"chrono",
"dialoguer"
"dialoguer",
]
[forminquire]
[plugins.forminquire]
upstream_url = "local"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "forminquire"
description = "Standalone interactive forms and prompts library + CLI tool - no Nushell dependency required"
version = "0.1.0"
category = "utility"
@ -108,16 +121,20 @@ commands = [
"forminquire custom",
"forminquire editor",
"forminquire date",
"forminquire form"
"forminquire form",
]
library_support = true
output_formats = ["text", "json", "yaml"]
output_formats = [
"text",
"json",
"yaml",
]
features = [
"8 interactive prompt types",
"TOML-based form definitions",
"Automatic stdin fallback",
"Multiple output formats",
"Both library and CLI usage"
"Both library and CLI usage",
]
dependencies = [
"clap",
@ -129,18 +146,343 @@ dependencies = [
"chrono",
"dialoguer",
"anyhow",
"thiserror"
"thiserror",
]
# Distribution Configuration
[distribution]
excluded_plugins = [
"nu_plugin_example"
[plugins.nu_plugin_nickel]
upstream_url = "local"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "nu_plugin_nickel"
description = "Nushell plugin for Nickel configuration language with eval, export, format, and validation"
version = "0.1.0"
category = "provisioning"
commands = [
"nickel-eval",
"nickel-export",
"nickel-format",
"nickel-validate",
"nickel-cache-status",
]
dependencies = [
"nu-plugin",
"nu-protocol",
"anyhow",
"tempfile",
"sha2",
"serde",
"serde_json",
"dirs",
"chrono",
]
features = [
"Config file evaluation with caching",
"Export Nickel to JSON/YAML",
"Format Nickel files",
"Validate Nickel projects",
"Cache status management",
]
[plugins.nu_plugin_nats]
upstream_url = "local"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "nu_plugin_nats"
description = "NATS JetStream plugin for provisioning platform event bus"
version = "0.110.0"
category = "provisioning"
commands = [
"nats stream setup",
"nats consumer setup",
"nats notify",
"nats pub",
"nats status",
]
dependencies = [
"async-nats",
"tokio",
"bytes",
"futures",
"serde_json",
]
features = [
"Idempotent stream + consumer provisioning",
"Pull drain of cli-notifications consumer",
"JetStream publish with stream+sequence ack",
"Live stream state for all 6 platform streams",
"NATS_SERVER env var or nats://127.0.0.1:4222 default",
]
[distribution]
excluded_plugins = ["nu_plugin_example"]
reason = "Reference/documentation plugin - excluded from distributions, installations, and collections. Still included in build and test for validation."
# Metadata
[settings]
nu_managed_dependencies = [
"nu-plugin",
"nu-protocol",
"nu-plugin-core",
"nu-plugin-protocol",
"nu-engine",
"nu-system",
"nu-path",
]
[registry]
version = "1.0.0"
updated = "2025-10-09"
format = "toml"
["plugins.nu_plugin_auth"]
upstream_url = "local"
upstream_branch = "main"
status = "error"
auto_merge = false
local_path = "nu_plugin_auth"
description = "Authentication plugin (JWT, MFA) for provisioning platform"
version = "0.1.0"
category = "provisioning"
commands = [
"auth login",
"auth logout",
"auth verify",
"auth sessions",
"auth mfa enroll",
"auth mfa verify",
]
dependencies = [
"jsonwebtoken",
"reqwest",
"keyring",
"rpassword",
"qrcode",
]
["plugins.nu_plugin_kms"]
upstream_url = "local"
upstream_branch = "main"
status = "error"
auto_merge = false
local_path = "nu_plugin_kms"
description = "KMS plugin (RustyVault, Age, Cosmian) for provisioning platform"
version = "0.1.0"
category = "provisioning"
backends = [
"rustyvault",
"age",
"cosmian",
"aws",
"vault",
]
commands = [
"kms encrypt",
"kms decrypt",
"kms generate-key",
"kms status",
]
dependencies = [
"reqwest",
"age",
"base64",
"serde",
]
["plugins.nu_plugin_orchestrator"]
upstream_url = "local"
upstream_branch = "main"
status = "error"
auto_merge = false
local_path = "nu_plugin_orchestrator"
description = "Orchestrator operations plugin (status, validate, tasks)"
version = "0.1.0"
category = "provisioning"
commands = [
"orch status",
"orch validate",
"orch tasks",
]
dependencies = [
"serde_json",
"serde_yaml",
"walkdir",
]
["plugins.nu_plugin_inquire"]
upstream_url = "https://github.com/jesusperezlorenzo/nu_plugin_inquire"
upstream_branch = "main"
status = "error"
auto_merge = false
local_path = "nu_plugin_inquire"
description = "Interactive forms and prompts plugin using inquire crate - solves TTY buffering issues"
version = "0.1.0"
category = "utility"
commands = [
"inquire text",
"inquire confirm",
"inquire select",
"inquire multi-select",
"inquire password",
"inquire custom",
"inquire editor",
"inquire date",
"inquire form",
]
dependencies = [
"inquire",
"serde",
"serde_json",
"toml",
"chrono",
"dialoguer",
]
["plugins.forminquire"]
upstream_url = "local"
upstream_branch = "main"
status = "error"
auto_merge = false
local_path = "forminquire"
description = "Standalone interactive forms and prompts library + CLI tool - no Nushell dependency required"
version = "0.1.0"
category = "utility"
type = "binary"
dual_mode = true
commands = [
"forminquire text",
"forminquire confirm",
"forminquire select",
"forminquire multi-select",
"forminquire password",
"forminquire custom",
"forminquire editor",
"forminquire date",
"forminquire form",
]
library_support = true
output_formats = [
"text",
"json",
"yaml",
]
features = [
"8 interactive prompt types",
"TOML-based form definitions",
"Automatic stdin fallback",
"Multiple output formats",
"Both library and CLI usage",
]
dependencies = [
"clap",
"inquire",
"serde",
"serde_json",
"serde_yaml",
"toml",
"chrono",
"dialoguer",
"anyhow",
"thiserror",
]
["plugins.nu_plugin_nickel"]
upstream_url = "local"
upstream_branch = "main"
status = "error"
auto_merge = false
local_path = "nu_plugin_nickel"
description = "Nushell plugin for Nickel configuration language with eval, export, format, and validation"
version = "0.1.0"
category = "provisioning"
commands = [
"nickel-eval",
"nickel-export",
"nickel-format",
"nickel-validate",
"nickel-cache-status",
]
dependencies = [
"nu-plugin",
"nu-protocol",
"anyhow",
"tempfile",
"sha2",
"serde",
"serde_json",
"dirs",
"chrono",
]
features = [
"Config file evaluation with caching",
"Export Nickel to JSON/YAML",
"Format Nickel files",
"Validate Nickel projects",
"Cache status management",
]
["plugins.nu_plugin_typedialog"]
upstream_url = "local"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "nu_plugin_typedialog"
description = "TypeDialog interactive forms and prompts plugin — replaces shlib TTY wrappers"
version = "0.110.0"
category = "provisioning"
commands = [
"typedialog form",
"typedialog nickel-roundtrip",
"typedialog text",
"typedialog confirm",
"typedialog select",
"typedialog multi-select",
"typedialog password",
]
dependencies = [
"nu-plugin",
"nu-protocol",
"typedialog-core",
"tokio",
"serde_json",
"thiserror",
"interprocess",
]
features = [
"CLI and web backends (--backend cli|web)",
"Nickel roundtrip with typecheck validation",
"Direct prompts: text, confirm, select, multi-select, password",
"ESC/cancel returns null (Value::nothing), not error",
"Initial values seeding via --initial <record>",
]
["plugins.nu_plugin_mcp"]
upstream_url = "local"
upstream_branch = "main"
status = "ok"
auto_merge = false
local_path = "nu_plugin_mcp"
description = "MCP client plugin — spawn and interact with provisioning-mcp-server via JSON-RPC 2.0"
version = "0.110.0"
category = "provisioning"
commands = [
"mcp connect",
"mcp tools list",
"mcp tool call",
"mcp disconnect",
]
dependencies = [
"nu-plugin",
"nu-protocol",
"serde_json",
"thiserror",
"interprocess",
]
features = [
"Spawn MCP server binary as child process",
"MCP protocol handshake (initialize + initialized)",
"Tool discovery via tools/list",
"Tool dispatch with record payload",
"Session state persisted in Arc<Mutex<Option<McpSession>>>",
"isError responses mapped to {error: true, message: ...} records",
]

File diff suppressed because one or more lines are too long

View File

@ -3,9 +3,11 @@
## Fundamental Rules for AI-Friendly Nushell Code
### Rule 1: One Command, One Purpose
Every command must do exactly one thing. AI agents struggle with multi-purpose functions.
```nushell
nushell
# ✅ GOOD - Single purpose
def extract-errors [log_file: path] -> table {
open $log_file | lines | parse "{time} [{level}] {msg}" | where level == "ERROR"
@ -18,9 +20,11 @@ def process-logs [log_file: path, --extract-errors, --count-warnings, --save-out
```
### Rule 2: Explicit Type Signatures Always
AI agents need clear contracts. Never omit types.
```nushell
nushell
# ✅ GOOD - Complete type information
def calculate-average [numbers: list<float>] -> float {
$numbers | math avg
@ -33,9 +37,11 @@ def calculate-average [numbers] {
```
### Rule 3: Return Early, Fail Fast
Check preconditions immediately. Don't nest error handling.
```nushell
nushell
# ✅ GOOD - Early returns
def process-file [path: path] -> table {
if not ($path | path exists) {
@ -66,9 +72,11 @@ def process-file [path: path] -> table {
## Essential Patterns for AI Code Generation
### Pattern 1: Command Template Pattern
Use this structure for EVERY command:
```nushell
nushell
# [PURPOSE]: Single-line description of what this does
# [INPUT]: Expected input format
# [OUTPUT]: Output format
@ -90,9 +98,11 @@ def command-name [
```
### Pattern 2: Pipeline Stage Pattern
Each pipeline stage must be self-contained and testable:
```nushell
nushell
# ✅ GOOD - Clear pipeline stages
def analyze-data [input: path] -> table {
load-data $input
@ -111,11 +121,13 @@ def format-output [data: table] -> table { $data | select relevant_columns }
```
### Pattern 3: Error Context Pattern
Always provide context for errors that AI can use to fix issues:
in Nushell 0.108, try-catch with error parameter might not be supported when assigning to variables.
```nushell
nushell
# ✅ GOOD - Detailed error context
def parse-config [config_path: path] -> record {
try {
@ -134,9 +146,11 @@ def parse-config [config_path: path] -> record {
```
### Pattern 4: Data Validation Pattern
Validate data structure at boundaries:
```nushell
nushell
# ✅ GOOD - Explicit validation
def process-user-data [data: table] -> table {
# Define expected schema
@ -163,9 +177,11 @@ def process-user-data [data: table] -> table {
## Critical Rules for AI Tool Integration
### Rule 4: No Side Effects in Functions
Functions must be pure unless explicitly named as mutations:
```nushell
nushell
# ✅ GOOD - Pure function
def calculate-tax [amount: float, rate: float] -> float {
$amount * $rate
@ -186,9 +202,11 @@ def calculate-tax [amount: float, rate: float] -> float {
```
### Rule 5: Atomic Operations
Every operation must be atomic - it either completely succeeds or completely fails:
```nushell
nushell
# ✅ GOOD - Atomic operation
def update-json-file [path: path, updates: record] -> nothing {
# Read, modify, write as single operation
@ -208,9 +226,11 @@ def update-json-file [path: path, updates: record] -> nothing {
```
### Rule 6: Explicit Dependencies
Never rely on global state or environment without declaring it:
```nushell
nushell
# ✅ GOOD - Explicit dependencies
def api-request [
endpoint: string
@ -232,9 +252,11 @@ def api-request [endpoint: string] -> any {
## Structured Data Patterns
### Pattern 5: Table Transformation Pattern
Always use Nushell's table operations instead of loops:
```nushell
nushell
# ✅ GOOD - Table operations
def transform-sales [sales: table] -> table {
$sales
@ -258,9 +280,11 @@ def transform-sales [sales: table] -> table {
```
### Pattern 6: Schema Definition Pattern
Define data schemas explicitly for AI understanding:
```nushell
nushell
# Schema definitions at module level
const USER_SCHEMA = {
id: "int"
@ -291,9 +315,11 @@ def validate-user [user: record] -> bool {
## AI-Specific Patterns
### Pattern 7: Self-Documenting Code Pattern
Include inline documentation that AI can parse:
```nushell
nushell
def complex-calculation [
data: table # @format: [{x: float, y: float, weight: float}]
] -> record { # @returns: {mean: float, std: float, correlation: float}
@ -318,9 +344,11 @@ def complex-calculation [
```
### Pattern 8: Testable Unit Pattern
Every function must include test examples:
```nushell
nushell
# Function with embedded test cases
def parse-version [version: string] -> record {
# @test: "1.2.3" -> {major: 1, minor: 2, patch: 3}
@ -342,9 +370,11 @@ def test-parse-version [] {
```
### Pattern 9: Incremental Computation Pattern
Break complex computations into verifiable steps:
```nushell
nushell
# ✅ GOOD - Each step is verifiable
def analyze-dataset [data: path] -> record {
# Load and get shape
@ -373,9 +403,11 @@ def analyze-dataset [data: path] -> record {
## Module Organization Rules
### Rule 7: Single Responsibility Modules
Each module handles one domain:
```nushell
nushell
# file: data_validation.nu
module data_validation {
export def validate-email [email: string] -> bool { }
@ -392,9 +424,11 @@ module data_transformation {
```
### Rule 8: Explicit Exports
Only export what's needed:
```nushell
nushell
module api_client {
# Public API
export def get [endpoint: string] -> any {
@ -415,9 +449,11 @@ module api_client {
## Performance Rules for AI Tools
### Rule 9: Lazy Evaluation
Don't compute until necessary:
```nushell
nushell
# ✅ GOOD - Lazy evaluation
def process-conditionally [
data: table
@ -436,9 +472,11 @@ def process-conditionally [
```
### Rule 10: Stream Large Data
Never load entire large files into memory:
```nushell
nushell
# ✅ GOOD - Streaming
def process-large-file [path: path] -> nothing {
open --raw $path
@ -460,9 +498,11 @@ def process-large-file [path: path] -> table {
## Error Handling Rules
### Rule 11: Never Swallow Errors
Always propagate or handle errors explicitly:
```nushell
nushell
# ✅ GOOD - Explicit error handling
def safe-divide [a: float, b: float] -> float {
if $b == 0 {
@ -478,9 +518,11 @@ def safe-divide [a: float, b: float] -> float {
```
### Rule 12: Structured Error Returns
Use consistent error structures:
```nushell
nushell
# Define error type
const ERROR_SCHEMA = {
success: "bool"
@ -512,9 +554,11 @@ def api-call [url: string] -> record {
## Code Generation Rules for AI
### Rule 13: Predictable Naming
Use consistent, predictable names:
```nushell
nushell
# Naming conventions AI can predict:
# - get-* : Returns data without modification
# - set-* : Updates data
@ -536,9 +580,11 @@ def parse-json [text: string] -> any { }
```
### Rule 14: Consistent Parameter Order
Always use this parameter order:
```nushell
nushell
# Order: required positional, optional positional, flags
def command [
required1: type # Required parameters first
@ -550,9 +596,11 @@ def command [
```
### Rule 15: Return Type Consistency
Use consistent return types for similar operations:
```nushell
nushell
# All getters return record or null
def get-config [] -> record? {
try { open config.json } catch { null }
@ -571,9 +619,11 @@ def transform-table [input: table] -> table {
```
### Rule 16: Function Signature Syntax with Pipeline Types (Nushell 0.107.1+)
**CRITICAL**: When using return type annotations, use the pipeline signature: `[params]: input_type -> return_type`
```nushell
nushell
# ✅ GOOD - Complete pipeline signature
def process-data [input: string]: nothing -> table {
$input | from json
@ -601,9 +651,11 @@ def process-data [input: string] {
**Note**: The syntax is `[parameters]: input_type -> return_type`. Both the colon AND arrow are required when specifying return types. This has been the syntax since Nushell 0.107.1.
### Rule 17: String Interpolation - Always Use Parentheses
**CRITICAL**: In string interpolations, ALWAYS use parentheses `($var)` or `($expr)` for ALL interpolations.
```nushell
nushell
# ✅ GOOD - Parentheses for variables
print $"Processing file ($filename) at ($timestamp)"
print $"Server ($hostname) running on port ($port)"
@ -624,12 +676,14 @@ print $"Server $hostname on port $port"
```
**Why**:
- Parentheses `($var)` or `($expr)` are the ONLY way to interpolate in Nushell strings
- Square brackets `[...]` are treated as literal characters (no interpolation)
- Both variables and expressions use the same syntax: `($something)`
- Consistent syntax reduces errors and improves maintainability
**Rule of thumb**:
- Variable? Use `($var)`
- Expression? Use `($expr)`
- Function call? Use `($fn arg)`
@ -640,24 +694,31 @@ print $"Server $hostname on port $port"
**CRITICAL**: In `$"..."` strings, parentheses that are NOT variable interpolation MUST be escaped.
**WRONG**:
```nushell
nushell
let msg = $"Update? (yes/no): " # ERROR: (yes/no) treated as command
let text = $"Use format (JSON/YAML)" # ERROR: tries to execute command
```
**CORRECT**:
```nushell
nushell
let msg = $"Update? \(yes/no\): " # Escaped literal parentheses
let text = $"Use format \(JSON/YAML\)" # Escaped literal parentheses
let msg = $"Value: ($var)" # Unescaped for variable interpolation
```
**Simple rule**:
- If `()` contains a variable name → keep as is: `($variable)`
- If `()` is literal text → escape with backslash: `\(text\)`
**Common mistake patterns:**
```nushell
nushell
# BAD - will try to execute 'yes/no' command
input $"Continue? (yes/no): "
@ -676,6 +737,7 @@ log_info $"Format \(example\): data"
## Quick Reference Card for AI Agents
```nushell
nushell
# TEMPLATE: Copy-paste this for new commands (Nushell 0.107.1+)
# [PURPOSE]:
# [INPUT]:
@ -716,6 +778,7 @@ def command-name [
## Summary Checklist for AI-Compatible Nushell
✅ **Every function has:**
- Explicit type signatures with pipeline syntax `[param: type]: input_type -> return_type {`
- Single responsibility
- Early validation
@ -723,6 +786,7 @@ def command-name [
- Test examples
✅ **Never use:**
- Global state without declaration
- Hidden side effects
- Nested conditionals (prefer early returns)
@ -733,6 +797,7 @@ def command-name [
- Function signatures without both colon AND arrow when specifying return types
✅ **Always prefer:**
- Pipeline operations over loops
- Pure functions over stateful
- Explicit over implicit
@ -741,6 +806,7 @@ def command-name [
- Parentheses `($var)` for ALL string interpolations (variables and expressions)
✅ **For AI tools specifically:**
- Use predictable naming patterns
- Include operation markers (! for mutations)
- Document schemas inline
@ -752,37 +818,36 @@ Following these rules and patterns ensures that AI agents like Claude Code can e
1. Try-Catch Block Pattern (10 files)
- Issue: Nushell 0.108 has stricter parsing for try-catch blocks
- Solution: Replace try {...} catch {...} with complete-based error handling:
- Issue: Nushell 0.108 has stricter parsing for try-catch blocks
- Solution: Replace try {...} catch {...} with complete-based error handling:
let result = (do { ... } | complete)
if $result.exit_code == 0 { $result.stdout } else { error_value }
- Files fixed:
- workspace/version.nu
- workspace/migration.nu (4 blocks)
- user/config.nu
- config/loader.nu
- oci/client.nu (8 blocks - OCI currently disabled)
- Files fixed:
- workspace/version.nu
- workspace/migration.nu (4 blocks)
- user/config.nu
- config/loader.nu
- oci/client.nu (8 blocks - OCI currently disabled)
2. Function Signature Syntax (2 instances)
1. Function Signature Syntax (2 instances)
- Issue: Missing input type in signatures
- Old: def foo [x: string] -> bool
- New: def foo [x: string]: nothing -> bool
- Files fixed: workspace/helpers.nu
- Issue: Missing input type in signatures
- Old: def foo [x: string] -> bool
- New: def foo [x: string]: nothing -> bool
- Files fixed: workspace/helpers.nu
3. Boolean Flag Syntax (1 instance)
1. Boolean Flag Syntax (1 instance)
- Issue: Type annotations not allowed on boolean flags
- Old: --flag: bool = true
- New: --flag = true
- Files fixed: main_provisioning/contexts.nu
- Issue: Type annotations not allowed on boolean flags
- Old: --flag: bool = true
- New: --flag = true
- Files fixed: main_provisioning/contexts.nu
4. Variable Type Initialization (1 instance)
- Issue: Can't assign record to null variable in 0.108
- Old: mut var = null; $var = {record}
- New: mut var = {success: false}; $var = {record}
1. Variable Type Initialization (1 instance)
- Issue: Can't assign record to null variable in 0.108
- Old: mut var = null; $var = {record}
- New: mut var = {success: false}; $var = {record}
#" Before (fails in Nushell 0.107.1)
try {
@ -793,7 +858,8 @@ Following these rules and patterns ensures that AI agents like Claude Code can e
default_value
}
# After (works in Nushell 0.107.1)
# After (works in Nushell 0.107.1)
let result = (do {
# operations
result

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,65 +1,64 @@
# Tools Module - Development Tools and Utilities
# Commands for development utilities, plugin management, and system tools
# 🔍 VERSION AND VALIDATION COMMANDS
# Check nushell version consistency
[no-cd]
validate-nushell:
@echo "🔍 Validating nushell version consistency..."
@{{justfile_directory()}}/scripts/run.sh --check-only
@{{ justfile_directory() }}/scripts/run.sh --check-only
# Fix nushell version mismatches
[no-cd]
fix-nushell:
@echo "🔧 Fixing nushell version mismatches..."
@{{justfile_directory()}}/scripts/run.sh --fix --check-only
@{{ justfile_directory() }}/scripts/run.sh --fix --check-only
# Update nu dependency versions
[no-cd]
update-nu-versions:
@echo "🔄 Updating nu dependency versions..."
@{{justfile_directory()}}/scripts/run.sh update_nu_versions.nu
@{{ justfile_directory() }}/scripts/run.sh update_nu_versions.nu
# List current nu dependency versions
[no-cd]
list-nu-versions:
@echo "📋 Current nu dependency versions:"
@{{justfile_directory()}}/scripts/run.sh update_nu_versions.nu list
@{{ justfile_directory() }}/scripts/run.sh update_nu_versions.nu list
# Update nushell submodule
[no-cd]
update-nushell:
@echo "🔄 Updating nushell submodule..."
@bash {{justfile_directory()}}/scripts/sh/update_nushell.sh update
@bash {{ justfile_directory() }}/scripts/sh/update_nushell.sh update
# 🆕 PLUGIN MANAGEMENT COMMANDS
# Create a new plugin from template
[no-cd]
make-plugin PLUGIN_NAME:
@echo "🆕 Creating new plugin: {{PLUGIN_NAME}}"
@{{justfile_directory()}}/scripts/run.sh make_plugin.nu {{PLUGIN_NAME}}
@echo "🆕 Creating new plugin: {{ PLUGIN_NAME }}"
@{{ justfile_directory() }}/scripts/run.sh make_plugin.nu {{ PLUGIN_NAME }}
# Install specific plugin locally (build + register)
[no-cd]
install-plugin PLUGIN:
@{{justfile_directory()}}/scripts/run.sh install_plugin.nu {{PLUGIN}} --verify
@{{ justfile_directory() }}/scripts/run.sh install_plugin.nu {{ PLUGIN }} --verify
# Register specific plugin with nushell
[no-cd]
register-plugin PLUGIN:
@{{justfile_directory()}}/scripts/run.sh register_plugins.nu register ./{{PLUGIN}}/target/release/{{PLUGIN}} --verify
@{{ justfile_directory() }}/scripts/run.sh register_plugins.nu register ./{{ PLUGIN }}/target/release/{{ PLUGIN }} --verify
# Register all built plugins with nushell
[no-cd]
register-all-plugins:
@{{justfile_directory()}}/scripts/run.sh register_plugins.nu register-all --verify
@{{ justfile_directory() }}/scripts/run.sh register_plugins.nu register-all --verify
# Verify plugin registration status
[no-cd]
verify-plugins:
@{{justfile_directory()}}/scripts/run.sh register_plugins.nu verify
@{{ justfile_directory() }}/scripts/run.sh register_plugins.nu verify
# Install plugins locally from distribution
[no-cd]
@ -83,7 +82,7 @@ install-local:
PLATFORM_DIR=$(find distribution -maxdepth 1 -type d -name "*-*" | head -n1)
fi
if [ -z "$PLATFORM_DIR" ] || [ ! -f "$PLATFORM_DIR/install.nu" ]; then
if [ -z "$PLATFORM_DIR" ] || [ ! -f "$PLATFORM_DIR/install.sh" ]; then
echo "❌ Installation script not found in distribution"
echo "💡 Run 'just collect' to regenerate distribution"
exit 1
@ -92,7 +91,7 @@ install-local:
echo "📦 Installing plugins locally from distribution..."
echo "💡 Using register-only mode (no sudo required)"
echo "💡 Using distribution: $PLATFORM_DIR"
cd "$PLATFORM_DIR" && nu install.nu --verify
cd "$PLATFORM_DIR" && bash install.sh --local
echo ""
echo "✅ Local installation completed!"
@ -120,7 +119,7 @@ install-system:
PLATFORM_DIR=$(find distribution -maxdepth 1 -type d -name "*-*" | head -n1)
fi
if [ -z "$PLATFORM_DIR" ] || [ ! -f "$PLATFORM_DIR/install.nu" ]; then
if [ -z "$PLATFORM_DIR" ] || [ ! -f "$PLATFORM_DIR/install.sh" ]; then
echo "❌ Installation script not found in distribution"
echo "💡 Run 'just collect' to regenerate distribution"
exit 1
@ -136,6 +135,7 @@ install-system:
# Copy nushell binary to user location (safer than system)
echo " Installing nushell binary to ~/.local/bin..."
rm -f ~/.local/bin/nu # Remove old version if it exists
cp "$PLATFORM_DIR/nu" ~/.local/bin/nu
chmod +x ~/.local/bin/nu
@ -169,7 +169,7 @@ install-system:
# Verify installation
echo " Verifying installation..."
~/.local/bin/nu -c "plugin list" | head -20
~/.local/bin/nu -c "plugin list" # | head -20
# Update PATH suggestion
echo ""
@ -186,7 +186,7 @@ install-from-archive ARCHIVE="":
#!/usr/bin/env bash
set -e
ARCHIVE="{{ARCHIVE}}"
ARCHIVE="{{ ARCHIVE }}"
# If no archive specified, find the latest one
if [ -z "$ARCHIVE" ]; then
@ -206,7 +206,7 @@ install-from-archive ARCHIVE="":
fi
echo "📦 Installing from archive: $ARCHIVE"
./install.sh --source-path "$ARCHIVE" --install-dir ~/.local --verify
./install.sh --source-path "$ARCHIVE" --install-dir ~/.local/bin --verify
# Install from bin_archives/ (fastest - everything already built)
[no-cd]
@ -230,7 +230,7 @@ install-fast:
echo "📍 Installing to: ~/.local/bin"
echo ""
./install.sh --source-path "$ARCHIVE" --install-dir ~/.local --verify
./install.sh --source-path "$ARCHIVE" --install-dir ~/.local/bin --verify
echo ""
echo "✅ Installation complete!"
@ -240,18 +240,18 @@ install-fast:
# Remove plugin from workspace
[no-cd]
remove-plugin PLUGIN:
@echo "🗑️ Removing plugin {{PLUGIN}}..."
@if [ -d "{{PLUGIN}}" ]; then \
echo "Are you sure you want to remove {{PLUGIN}}? (y/N)"; \
@echo "🗑️ Removing plugin {{ PLUGIN }}..."
@if [ -d "{{ PLUGIN }}" ]; then \
echo "Are you sure you want to remove {{ PLUGIN }}? (y/N)"; \
read -r confirm; \
if [ "$$confirm" = "y" ] || [ "$$confirm" = "Y" ]; then \
rm -rf "{{PLUGIN}}"; \
echo "✅ Removed {{PLUGIN}}"; \
rm -rf "{{ PLUGIN }}"; \
echo "✅ Removed {{ PLUGIN }}"; \
else \
echo "❌ Cancelled"; \
fi; \
else \
echo "Plugin {{PLUGIN}} not found"; \
echo "Plugin {{ PLUGIN }} not found"; \
fi
# List all plugins
@ -267,40 +267,40 @@ list-plugins:
# Show plugin information
[no-cd]
plugin-info PLUGIN:
@echo " Plugin Information: {{PLUGIN}}"
@if [ -d "{{PLUGIN}}" ]; then \
echo "Name: {{PLUGIN}}"; \
echo "Path: $(pwd)/{{PLUGIN}}"; \
if [ -f "{{PLUGIN}}/Cargo.toml" ]; then \
echo "Version: $(grep '^version' {{PLUGIN}}/Cargo.toml | cut -d'"' -f2)"; \
echo "Description: $(grep '^description' {{PLUGIN}}/Cargo.toml | cut -d'"' -f2 || echo 'Not available')"; \
@echo " Plugin Information: {{ PLUGIN }}"
@if [ -d "{{ PLUGIN }}" ]; then \
echo "Name: {{ PLUGIN }}"; \
echo "Path: $(pwd)/{{ PLUGIN }}"; \
if [ -f "{{ PLUGIN }}/Cargo.toml" ]; then \
echo "Version: $(grep '^version' {{ PLUGIN }}/Cargo.toml | cut -d'"' -f2)"; \
echo "Description: $(grep '^description' {{ PLUGIN }}/Cargo.toml | cut -d'"' -f2 || echo 'Not available')"; \
fi; \
echo "Binary exists: $([ -f {{PLUGIN}}/target/release/{{PLUGIN}} ] && echo 'Yes' || echo 'No')"; \
echo "Last modified: $(stat -c %y {{PLUGIN}} 2>/dev/null || stat -f %m {{PLUGIN}} 2>/dev/null || echo 'Unknown')"; \
echo "Binary exists: $([ -f {{ PLUGIN }}/target/release/{{ PLUGIN }} ] && echo 'Yes' || echo 'No')"; \
echo "Last modified: $(stat -c %y {{ PLUGIN }} 2>/dev/null || stat -f %m {{ PLUGIN }} 2>/dev/null || echo 'Unknown')"; \
else \
echo "Plugin {{PLUGIN}} not found"; \
echo "Plugin {{ PLUGIN }} not found"; \
fi
# 🛠️ DEVELOPMENT TOOLS
# Run command on specific plugin
plugin PLUGIN CMD:
@echo "🔧 Running '{{CMD}}' on {{PLUGIN}}"
@cd {{PLUGIN}} && {{CMD}}
@echo "🔧 Running '{{ CMD }}' on {{ PLUGIN }}"
@cd {{ PLUGIN }} && {{ CMD }}
# Check specific plugin
[no-cd]
check-plugin PLUGIN:
@echo "🔍 Checking {{PLUGIN}}..."
@cd {{PLUGIN}} && cargo check
@echo "🔍 Checking {{ PLUGIN }}..."
@cd {{ PLUGIN }} && cargo check
# Watch for changes and rebuild (requires entr)
[no-cd]
watch PLUGIN:
#!/usr/bin/env bash
if command -v entr >/dev/null 2>&1; then
echo "👀 Watching {{PLUGIN}} for changes..."
find {{PLUGIN}}/src -name "*.rs" | entr just build-plugin {{PLUGIN}}
echo "👀 Watching {{ PLUGIN }} for changes..."
find {{ PLUGIN }}/src -name "*.rs" | entr just build-plugin {{ PLUGIN }}
else
echo "❌ entr not installed. Install with: brew install entr (macOS) or apt install entr (Ubuntu)"
fi
@ -420,12 +420,12 @@ clean:
# Clean specific plugin
[no-cd]
clean-plugin PLUGIN:
@echo "🧹 Cleaning {{PLUGIN}}..."
@if [ -d "{{PLUGIN}}/target" ]; then \
echo "Removing {{PLUGIN}}/target..."; \
rm -rf "{{PLUGIN}}/target"; \
@echo "🧹 Cleaning {{ PLUGIN }}..."
@if [ -d "{{ PLUGIN }}/target" ]; then \
echo "Removing {{ PLUGIN }}/target..."; \
rm -rf "{{ PLUGIN }}/target"; \
else \
echo "No target directory found for {{PLUGIN}}"; \
echo "No target directory found for {{ PLUGIN }}"; \
fi
# Clean distribution files
@ -498,8 +498,8 @@ config-list:
# Edit configuration file
[no-cd]
config-edit FILE:
@echo "✏️ Editing configuration: {{FILE}}"
@${EDITOR:-nano} {{FILE}}
@echo "✏️ Editing configuration: {{ FILE }}"
@${EDITOR:-nano} {{ FILE }}
# Backup configuration
[no-cd]

View File

@ -262,6 +262,20 @@ list-versions:
@echo "📋 Plugin Version List"
@nu {{justfile_directory()}}/scripts/update_all_plugins.nu --list
# Replace [package] version from old to new (only exact matches)
[no-cd]
[group('version-update')]
replace-package-version FROM TO:
@echo "🔄 Replacing [package] version from {{FROM}} to {{TO}}"
@nu {{justfile_directory()}}/scripts/update_all_plugins.nu replace-version {{FROM}} {{TO}}
# Preview [package] version replacement (dry-run)
[no-cd]
[group('version-update')]
preview-package-version FROM TO:
@echo "🔍 Preview: Replacing [package] version from {{FROM}} to {{TO}}"
@nu {{justfile_directory()}}/scripts/update_all_plugins.nu replace-version {{FROM}} {{TO}} --dry-run
# 📦 DISTRIBUTION CREATION
# Create complete distribution packages (nushell + all plugins)
@ -480,6 +494,10 @@ update-help:
@echo " just dist-status - Show distribution status"
@echo " just list-versions - List plugin versions"
@echo ""
@echo "Package Version Management:"
@echo " just replace-package-version 0.109.1 0.110.0 - Replace [package] version"
@echo " just preview-package-version 0.109.1 0.110.0 - Preview replacement (dry-run)"
@echo ""
@echo "Documentation:"
@echo " just update-docs - Show documentation paths"
@echo " cat guides/COMPLETE_VERSION_UPDATE_GUIDE.md"

View File

@ -1,6 +1,6 @@
[package]
name = "nu_plugin_auth"
version = "0.109.1"
version = "0.111.0"
authors = ["Jesus Perez <jesus@librecloud.online>"]
edition = "2021"
description = "Nushell plugin for provisioning authentication (JWT, MFA)"
@ -8,8 +8,8 @@ repository = "https://github.com/provisioning/nu_plugin_auth"
license = "MIT"
[dependencies]
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
jsonwebtoken = "=9.3"
serde_json = "1.0"
keyring = "3.6"
@ -39,6 +39,5 @@ features = ["full"]
version = "5.7"
features = ["qr"]
[dev-dependencies.nu-plugin-test-support]
version = "0.109.1"
path = "../nushell/crates/nu-plugin-test-support"
[dev-dependencies]
nu-plugin-test-support = "0.111.0"

View File

@ -1,44 +0,0 @@
[package]
name = "nu_plugin_auth"
version = "0.109.0"
authors = ["Jesus Perez <jesus@librecloud.online>"]
edition = "2021"
description = "Nushell plugin for provisioning authentication (JWT, MFA)"
repository = "https://github.com/provisioning/nu_plugin_auth"
license = "MIT"
[dependencies]
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
jsonwebtoken = "=9.3"
serde_json = "1.0"
keyring = "3.6"
rpassword = "7.4"
base64 = "0.22"
qrcode = "0.14"
chrono = "0.4"
[dependencies.reqwest]
version = "0.12"
features = [
"json",
"rustls-tls",
"blocking",
]
default-features = false
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.tokio]
version = "1.48"
features = ["full"]
[dependencies.totp-rs]
version = "5.7"
features = ["qr"]
[dev-dependencies.nu-plugin-test-support]
version = "0.109.0"
path = "../nushell/crates/nu-plugin-test-support"

View File

@ -21,12 +21,16 @@ This plugin provides native Nushell commands for authenticating with the provisi
Login to provisioning platform with JWT authentication.
**Syntax:**
```nushell
nushell
auth login <username> [password] [--url <control-center-url>] [--save]
```
**Examples:**
```nushell
nushell
# Login with password prompt (secure)
auth login admin
@ -45,12 +49,16 @@ auth login admin --save
Logout from provisioning platform (revoke tokens).
**Syntax:**
```nushell
nushell
auth logout [--all]
```
**Examples:**
```nushell
nushell
# Logout from current session
auth logout
@ -63,12 +71,16 @@ auth logout --all
Verify current authentication token.
**Syntax:**
```nushell
nushell
auth verify [--token <jwt-token>]
```
**Examples:**
```nushell
nushell
# Verify stored authentication token
auth verify
@ -81,12 +93,16 @@ auth verify --token eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
List active authentication sessions.
**Syntax:**
```nushell
nushell
auth sessions [--active]
```
**Examples:**
```nushell
nushell
# List all sessions
auth sessions
@ -98,7 +114,8 @@ auth sessions --active
### Build from source
```bash
```nushell
bash
cd provisioning/core/plugins/nushell-plugins/nu_plugin_auth
cargo build --release
```
@ -106,13 +123,15 @@ cargo build --release
### Register with Nushell
```nushell
nushell
plugin add target/release/nu_plugin_auth
plugin use auth
```
### Using justfile (recommended)
```bash
```nushell
bash
# From nushell-plugins directory
just install-plugin nu_plugin_auth
@ -131,6 +150,7 @@ The plugin uses the following defaults:
Override defaults using command flags:
```nushell
nushell
# Use custom control center URL
auth login admin --url https://control.production.example.com
```
@ -167,6 +187,7 @@ See control center API documentation for details: `provisioning/platform/control
**Version**: 0.1.0 (Initial structure)
**Implementation Progress**:
- ✅ Plugin structure created (Agente 1)
- ⏳ Login command implementation (Agente 2)
- ⏳ Logout command implementation (Agente 3)

File diff suppressed because one or more lines are too long

View File

@ -10,6 +10,7 @@
## ✅ Completed Components
### 1. Login Command (`auth login`)
- [x] Username/password authentication
- [x] Secure password prompt (no echo)
- [x] HTTP POST to `/auth/login`
@ -20,6 +21,7 @@
- [x] Error handling (HTTP errors, keyring errors)
### 2. Logout Command (`auth logout`)
- [x] Token retrieval from keyring
- [x] HTTP POST to `/auth/logout`
- [x] Token revocation on server
@ -29,6 +31,7 @@
- [x] Error handling (no session, HTTP errors)
### 3. Helper Functions (`src/helpers.rs`)
- [x] `store_tokens_in_keyring()` - Save JWT tokens securely
- [x] `get_access_token()` - Retrieve access token
- [x] `get_tokens_from_keyring()` - Retrieve both tokens
@ -40,6 +43,7 @@
- [x] `list_sessions()` - HTTP sessions API (ready for future use)
### 4. MFA Support (BONUS)
- [x] `send_mfa_enroll_request()` - TOTP/WebAuthn enrollment
- [x] `send_mfa_verify_request()` - TOTP code verification
- [x] `generate_qr_code()` - QR code generation for TOTP
@ -48,6 +52,7 @@
- [x] `auth mfa verify` command
### 5. Security Features
- [x] OS keyring integration (macOS Keychain, Linux libsecret, Windows Credential Manager)
- [x] Secure password input (rpassword crate)
- [x] HTTPS with rustls-tls
@ -56,6 +61,7 @@
- [x] Server-side token revocation
### 6. Documentation
- [x] `LOGIN_LOGOUT_IMPLEMENTATION.md` - Complete implementation details
- [x] `QUICK_REFERENCE.md` - Command reference card
- [x] `IMPLEMENTATION_STATUS.md` - This status file
@ -67,7 +73,9 @@
## 🔧 Build Status
### Compilation
```bash
```nushell
bash
$ cargo check
Checking nu_plugin_auth v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.89s
@ -81,6 +89,7 @@ $ cargo build --release
**Warnings**: 6 unused code warnings (for future commands)
### Dependencies
- ✅ `reqwest` with `blocking` feature
- ✅ `keyring = "3.2"` for OS credential storage
- ✅ `rpassword = "7.4"` for secure input
@ -93,13 +102,17 @@ $ cargo build --release
## 📝 Test Instructions
### 1. Register Plugin
```nushell
nushell
plugin add target/release/nu_plugin_auth
plugin use nu_plugin_auth
```
### 2. Test Login
```nushell
nushell
# Interactive password prompt
auth login admin
@ -111,7 +124,9 @@ auth login admin --url http://control.example.com:8081
```
### 3. Test Logout
```nushell
nushell
# Logout current user
auth logout
@ -125,7 +140,9 @@ auth logout --all
### 4. Expected Output
**Login Success:**
```nushell
nushell
{
success: true,
user: {
@ -140,7 +157,9 @@ auth logout --all
```
**Logout Success:**
```nushell
nushell
{
success: true,
message: "Logged out successfully",
@ -153,6 +172,7 @@ auth logout --all
## 🚀 Integration Points
### Control Center API
- **Base URL**: `http://localhost:8081` (default)
- **Endpoints**:
- `POST /auth/login` - Authentication
@ -163,6 +183,7 @@ auth logout --all
- `POST /mfa/verify` - MFA verification
### Security System
- **JWT Auth**: RS256-signed tokens (15min access, 7d refresh)
- **MFA**: TOTP (RFC 6238) + WebAuthn/FIDO2
- **Audit**: All auth events logged
@ -173,12 +194,14 @@ auth logout --all
## ⏭️ Future Work (Not Implemented)
### Commands to Implement
- [ ] `auth verify` - Verify current token validity
- [ ] `auth sessions` - List all active sessions
- [ ] `auth whoami` - Show current user from token
- [ ] `auth refresh` - Refresh expired access token
### Enhancements
- [ ] Auto-refresh tokens before expiration
- [ ] Background token refresh daemon
- [ ] Session management (revoke specific session)
@ -292,6 +315,7 @@ Beyond the basic requirements:
**Status**: ✅ PRODUCTION READY
**Ready for**:
- Manual testing with Control Center
- Integration testing
- User acceptance testing

View File

@ -20,27 +20,32 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
**Implemented Functions:**
#### Token Storage (Keyring Integration)
- `store_tokens_in_keyring()` - Store JWT tokens in OS keyring
- `get_access_token()` - Retrieve access token from keyring
- `get_tokens_from_keyring()` - Retrieve both tokens from keyring
- `remove_tokens_from_keyring()` - Delete tokens from keyring
#### Password Input
- `prompt_password()` - Secure password input (no echo)
#### HTTP API Calls
- `send_login_request()` - POST `/auth/login` with credentials
- `send_logout_request()` - POST `/auth/logout` to revoke token
- `verify_token()` - GET `/auth/verify` to check token validity
- `list_sessions()` - GET `/auth/sessions` to list active sessions
#### MFA Support (Bonus)
- `send_mfa_enroll_request()` - POST `/mfa/enroll/{type}` for TOTP/WebAuthn
- `send_mfa_verify_request()` - POST `/mfa/verify` to verify TOTP code
- `generate_qr_code()` - Generate QR code for TOTP secret
- `display_qr_code()` - Display QR code in terminal with instructions
**Data Structures:**
- `LoginRequest` - Login payload
- `TokenResponse` - Login response with JWT tokens and user info
- `UserInfo` - User details (id, username, email, roles)
@ -59,13 +64,16 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
**Implemented Commands:**
#### `auth login` Command (Lines 92-149)
**Signature:**
- Required: `username: String`
- Optional: `password: String` (prompts if omitted)
- Flag: `--url <string>` (default: `http://localhost:8081`)
- Switch: `--save` (save tokens to keyring)
**Behavior:**
1. Get username from first argument
2. Get password from second argument OR prompt securely
3. Send login request to Control Center
@ -73,7 +81,9 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
5. Return success response with user info and token metadata
**Example Output:**
```nushell
nushell
{
success: true,
user: {
@ -88,12 +98,15 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
```
#### `auth logout` Command (Lines 193-234)
**Signature:**
- Flag: `--user <string>` (default: current system user)
- Flag: `--url <string>` (default: `http://localhost:8081`)
- Switch: `--all` (logout all sessions)
**Behavior:**
1. Get username from flag or environment variable `$USER`
2. Retrieve access token from keyring
3. Send logout request to Control Center
@ -101,7 +114,9 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
5. Return success confirmation
**Example Output:**
```nushell
nushell
{
success: true,
message: "Logged out successfully",
@ -114,6 +129,7 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
### 3. `Cargo.toml`
**Dependencies Added:**
- `reqwest` with `blocking` feature enabled for synchronous HTTP
- `keyring = "3.2"` for OS-level credential storage
- `rpassword = "7.4"` for secure password input
@ -149,6 +165,7 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
**Base URL**: Configurable via `--url` flag (default: `http://localhost:8081`)
**Endpoints Used:**
- `POST /auth/login` - Authenticate and receive JWT tokens
- `POST /auth/logout` - Revoke access token
- `GET /auth/verify` - Verify token validity
@ -157,7 +174,9 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
- `POST /mfa/verify` - Verify MFA code
**Request Format:**
```json
```nushell
json
// Login
{
"username": "admin",
@ -171,7 +190,9 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
```
**Response Format:**
```json
```nushell
json
// Login success
{
"access_token": "eyJhbGc...",
@ -189,13 +210,16 @@ Complete implementation of Login and Logout commands for the `nu_plugin_auth` Nu
### Error Handling
**Comprehensive error messages:**
- HTTP request failures with status codes
- Keyring errors (access denied, not found)
- Password input errors
- API response parsing errors
**Example Error Flow:**
```nushell
nushell
# No active session
auth logout
# Error: No active session: No token found
@ -215,7 +239,8 @@ auth login admin --url http://invalid:8081
✅ **Successful Compilation**
```bash
```nushell
bash
$ cargo check
Checking nu_plugin_auth v0.1.0
Finished `dev` profile [unoptimized + debuginfo] target(s) in 2.89s
@ -236,53 +261,70 @@ $ cargo build --release
### Manual Testing
#### 1. Register Plugin
```nushell
nushell
plugin add target/release/nu_plugin_auth
plugin use nu_plugin_auth
```
#### 2. Test Login (Password Prompt)
```nushell
nushell
auth login admin
# Password: ******
# Returns user info and token metadata
```
#### 3. Test Login (Password in Command)
```nushell
nushell
auth login admin mypassword --save
# Saves tokens to keyring
```
#### 4. Test Login (Custom URL)
```nushell
nushell
auth login admin --url http://control.example.com:8081
```
#### 5. Test Logout
```nushell
nushell
auth logout
# Logs out current user
```
#### 6. Test Logout (Specific User)
```nushell
nushell
auth logout --user admin
```
#### 7. Test Logout (All Sessions)
```nushell
nushell
auth logout --all
```
### Integration Testing
**Prerequisites:**
- Control Center running at `http://localhost:8081`
- Valid user account (username + password)
**Test Workflow:**
```nushell
nushell
# 1. Login
let login_result = auth login testuser testpass --save
@ -324,7 +366,9 @@ assert ($logout_result.message == "Logged out successfully")
### MFA Support (Bonus Implementation)
**TOTP Enrollment:**
```nushell
nushell
# Enroll in TOTP (Google Authenticator, Authy)
auth mfa enroll totp --user admin
@ -333,13 +377,17 @@ auth mfa enroll totp --user admin
```
**TOTP Verification:**
```nushell
nushell
# Verify 6-digit TOTP code
auth mfa verify --code 123456 --user admin
```
**WebAuthn Enrollment:**
```nushell
nushell
# Enroll WebAuthn (YubiKey, Touch ID, Windows Hello)
auth mfa enroll webauthn --user admin
```
@ -349,18 +397,22 @@ auth mfa enroll webauthn --user admin
## Future Enhancements (Not Implemented)
### Token Refresh
- Auto-refresh expired access tokens using refresh token
- Background refresh before expiration
### Session Management
- `auth sessions` - List all active sessions
- `auth sessions --revoke <id>` - Revoke specific session
### Token Verification
- `auth verify` - Check if current token is valid
- `auth whoami` - Show current user info from token
### Certificate Pinning
- Pin Control Center TLS certificate
- Prevent MITM attacks
@ -369,6 +421,7 @@ auth mfa enroll webauthn --user admin
## Dependencies
### Runtime Dependencies
- `keyring = "3.2"` - OS credential storage
- `rpassword = "7.4"` - Secure password input
- `reqwest = "0.12"` - HTTP client (blocking mode)
@ -377,10 +430,12 @@ auth mfa enroll webauthn --user admin
- `qrcode = "0.14"` - QR code generation
### Build Dependencies
- Rust 1.70+ (stable)
- Nushell 0.107.1 (via path dependency)
### Platform Requirements
- macOS: Keychain access
- Linux: libsecret/gnome-keyring
- Windows: Credential Manager
@ -392,7 +447,9 @@ auth mfa enroll webauthn --user admin
### Command Help
#### Login Command
```nushell
nushell
help auth login
# Usage:
@ -417,7 +474,9 @@ help auth login
```
#### Logout Command
```nushell
nushell
help auth logout
# Usage:

File diff suppressed because one or more lines are too long

View File

@ -8,6 +8,7 @@
## Installation
```nushell
nushell
# Build plugin
cargo build --release -p nu_plugin_auth
@ -21,7 +22,9 @@ plugin use nu_plugin_auth
## Login Command
### Basic Usage
```nushell
nushell
# Interactive login (password prompt)
auth login admin
@ -36,13 +39,16 @@ auth login admin --url http://control.example.com:8081
```
### Flags
| Flag | Short | Type | Description | Default |
|------|-------|------|-------------|---------|
| `--url` | - | String | Control Center URL | `http://localhost:8081` |
| `--save` | - | Switch | Save tokens to keyring | `false` |
### Output
```nushell
nushell
{
success: true,
user: {
@ -61,7 +67,9 @@ auth login admin --url http://control.example.com:8081
## Logout Command
### Basic Usage
```nushell
nushell
# Logout current user
auth logout
@ -73,6 +81,7 @@ auth logout --all
```
### Flags
| Flag | Short | Type | Description | Default |
|------|-------|------|-------------|---------|
| `--user` | `-u` | String | Username | Current system user |
@ -80,7 +89,9 @@ auth logout --all
| `--all` | `-a` | Switch | Logout all sessions | `false` |
### Output
```nushell
nushell
{
success: true,
message: "Logged out successfully",
@ -93,7 +104,9 @@ auth logout --all
## MFA Commands (Bonus)
### TOTP Enrollment
```nushell
nushell
# Enroll in TOTP
auth mfa enroll totp
@ -104,7 +117,9 @@ auth mfa enroll totp --user alice
**Output**: QR code in terminal + secret + backup codes
### TOTP Verification
```nushell
nushell
# Verify TOTP code
auth mfa verify --code 123456
@ -113,7 +128,9 @@ auth mfa verify --code 123456 --user alice
```
### WebAuthn Enrollment
```nushell
nushell
# Enroll WebAuthn (YubiKey, Touch ID)
auth mfa enroll webauthn
```
@ -133,6 +150,7 @@ auth mfa enroll webauthn
## Error Handling
```nushell
nushell
# No active session
auth logout
# Error: No active session: No token found
@ -174,7 +192,9 @@ auth login admin --url http://invalid:8081
## Workflow Examples
### Standard Login/Logout
```nushell
nushell
# Login
auth login admin --save
@ -185,7 +205,9 @@ auth logout
```
### Multiple Users
```nushell
nushell
# Login as different users
auth login alice --save
auth login bob --save
@ -195,7 +217,9 @@ auth logout --user alice
```
### CI/CD Integration
```nushell
nushell
# Non-interactive login
let token = auth login $env.CI_USER $env.CI_PASS | get user.id
@ -210,18 +234,22 @@ auth logout --user $env.CI_USER
## Troubleshooting
### "No token found" error
**Cause**: No active session or keyring not accessible
**Fix**: Login again with `--save` flag
### "HTTP request failed"
**Cause**: Control Center not running or wrong URL
**Fix**: Check Control Center status and `--url` flag
### "Login failed: HTTP 401"
**Cause**: Invalid credentials
**Fix**: Verify username and password
### Keyring access denied
**Cause**: OS permission issue
**Fix**: Grant keychain/keyring access to plugin binary
@ -230,7 +258,9 @@ auth logout --user $env.CI_USER
## Development
### Build Commands
```bash
```nushell
bash
# Check code
cargo check -p nu_plugin_auth
@ -245,6 +275,7 @@ cargo test -p nu_plugin_auth
```
### Plugin Location
- Source: `provisioning/core/plugins/nushell-plugins/nu_plugin_auth/`
- Binary: `target/release/nu_plugin_auth`

View File

@ -2,7 +2,8 @@
## Installation
```bash
```nushell
bash
# Build plugin
cargo build --release
@ -15,7 +16,8 @@ plugin use auth
### TOTP Enrollment
```bash
```nushell
bash
# Enroll with QR code
auth mfa enroll totp
@ -30,7 +32,8 @@ auth mfa enroll totp --url http://control.example.com:8081
### TOTP Verification
```bash
```nushell
bash
# Verify code
auth mfa verify --code 123456
@ -42,7 +45,8 @@ auth mfa verify --code 123456 --user alice
## Complete Workflow
```bash
```nushell
bash
# 1. Login
auth login admin --save

View File

@ -93,7 +93,9 @@ impl VerificationResult {
pub fn decode_claims_unverified(token: &str) -> Result<Claims, AuthError> {
let parts: Vec<&str> = token.split('.').collect();
if parts.len() != 3 {
return Err(AuthError::invalid_token("Token must have 3 parts separated by '.'"));
return Err(AuthError::invalid_token(
"Token must have 3 parts separated by '.'",
));
}
let payload = parts[1];
@ -118,7 +120,10 @@ pub fn decode_claims_unverified(token: &str) -> Result<Claims, AuthError> {
///
/// Returns a VerificationResult indicating whether the token is valid
/// and containing the claims if verification succeeded.
pub fn verify_token_rs256(token: &str, public_key_pem: &str) -> Result<VerificationResult, AuthError> {
pub fn verify_token_rs256(
token: &str,
public_key_pem: &str,
) -> Result<VerificationResult, AuthError> {
// Verify the token header uses RS256
let header = decode_header(token)
.map_err(|e| AuthError::invalid_token(format!("Failed to decode header: {}", e)))?;

View File

@ -206,9 +206,15 @@ mod tests {
#[test]
fn test_error_kind_display() {
assert_eq!(AuthErrorKind::InvalidCredentials.to_string(), "invalid credentials");
assert_eq!(
AuthErrorKind::InvalidCredentials.to_string(),
"invalid credentials"
);
assert_eq!(AuthErrorKind::TokenExpired.to_string(), "token expired");
assert_eq!(AuthErrorKind::KeyringError.to_string(), "keyring operation failed");
assert_eq!(
AuthErrorKind::KeyringError.to_string(),
"keyring operation failed"
);
}
#[test]

View File

@ -248,7 +248,7 @@ pub fn verify_token(url: &str, token: &str) -> Result<VerifyResponse, AuthError>
pub fn list_sessions(
url: &str,
token: &str,
active_only: bool
active_only: bool,
) -> Result<Vec<SessionInfo>, AuthError> {
let client = create_client()?;
@ -365,8 +365,12 @@ pub fn prompt_password(prompt: &str) -> Result<String, AuthError> {
.flush()
.map_err(|e| AuthError::new(AuthErrorKind::InternalError, format!("Flush error: {}", e)))?;
rpassword::read_password()
.map_err(|e| AuthError::new(AuthErrorKind::InternalError, format!("Password read error: {}", e)))
rpassword::read_password().map_err(|e| {
AuthError::new(
AuthErrorKind::InternalError,
format!("Password read error: {}", e),
)
})
}
// =============================================================================
@ -386,8 +390,12 @@ pub fn generate_qr_code(uri: &str) -> Result<String, AuthError> {
use qrcode::render::unicode;
use qrcode::QrCode;
let code = QrCode::new(uri)
.map_err(|e| AuthError::new(AuthErrorKind::InternalError, format!("QR code generation failed: {}", e)))?;
let code = QrCode::new(uri).map_err(|e| {
AuthError::new(
AuthErrorKind::InternalError,
format!("QR code generation failed: {}", e),
)
})?;
let qr_string = code
.render::<unicode::Dense1x2>()
@ -425,10 +433,12 @@ fn extract_secret(uri: &str) -> Result<String, AuthError> {
.nth(1)
.and_then(|s| s.split('&').next())
.map(|s| s.to_string())
.ok_or_else(|| AuthError::new(
AuthErrorKind::InternalError,
"Failed to extract secret from URI",
))
.ok_or_else(|| {
AuthError::new(
AuthErrorKind::InternalError,
"Failed to extract secret from URI",
)
})
}
// =============================================================================
@ -437,6 +447,21 @@ fn extract_secret(uri: &str) -> Result<String, AuthError> {
use nu_protocol::{record, Span, Value};
/// Converts a UserInfo to a Nushell Value record.
pub fn user_info_to_value(user: &UserInfo, span: Span) -> Value {
Value::record(
record! {
"id" => Value::string(&user.id, span),
"username" => Value::string(&user.username, span),
"email" => Value::string(&user.email, span),
"roles" => Value::list(
user.roles.iter().map(|r| Value::string(r, span)).collect(),
span,
),
},
span,
)
}
/// Converts a SessionInfo to a Nushell Value record.
pub fn session_info_to_value(session: &SessionInfo, span: Span) -> Value {

View File

@ -78,12 +78,12 @@ pub fn get_access_token(username: &str) -> Result<String, AuthError> {
let entry = Entry::new(SERVICE_NAME_ACCESS, username)
.map_err(|e| AuthError::keyring_error(format!("Failed to access keyring: {}", e)))?;
entry
.get_password()
.map_err(|e| AuthError::new(
entry.get_password().map_err(|e| {
AuthError::new(
AuthErrorKind::SessionNotFound,
format!("No access token found for user '{}': {}", username, e),
))
)
})
}
/// Retrieves the refresh token from the system keyring.
@ -100,12 +100,12 @@ pub fn get_refresh_token(username: &str) -> Result<String, AuthError> {
let entry = Entry::new(SERVICE_NAME_REFRESH, username)
.map_err(|e| AuthError::keyring_error(format!("Failed to access keyring: {}", e)))?;
entry
.get_password()
.map_err(|e| AuthError::new(
entry.get_password().map_err(|e| {
AuthError::new(
AuthErrorKind::SessionNotFound,
format!("No refresh token found for user '{}': {}", username, e),
))
)
})
}
/// Retrieves both access and refresh tokens from the system keyring.
@ -175,9 +175,9 @@ pub fn get_public_key(key_id: &str) -> Result<String, AuthError> {
let entry = Entry::new(SERVICE_NAME_PUBLIC_KEY, key_id)
.map_err(|e| AuthError::keyring_error(format!("Failed to access keyring: {}", e)))?;
entry
.get_password()
.map_err(|e| AuthError::configuration_error(format!("Public key '{}' not found: {}", key_id, e)))
entry.get_password().map_err(|e| {
AuthError::configuration_error(format!("Public key '{}' not found: {}", key_id, e))
})
}
/// Checks if tokens exist for a user.

View File

@ -128,8 +128,7 @@ impl SimplePluginCommand for Login {
let password = if let Some(pwd) = password_arg {
pwd
} else {
helpers::prompt_password("Password: ")
.map_err(|e| LabeledError::new(e.to_string()))?
helpers::prompt_password("Password: ").map_err(|e| LabeledError::new(e.to_string()))?
};
// Send login request
@ -234,16 +233,15 @@ impl SimplePluginCommand for Logout {
let username = username_arg.unwrap_or_else(keyring::get_current_username);
// Get access token
let access_token = keyring::get_access_token(&username)
.map_err(|e| LabeledError::new(e.to_string()))?;
let access_token =
keyring::get_access_token(&username).map_err(|e| LabeledError::new(e.to_string()))?;
// Send logout request
helpers::send_logout_request(&url, &access_token)
.map_err(|e| LabeledError::new(e.to_string()))?;
// Remove tokens from keyring
keyring::remove_tokens(&username)
.map_err(|e| LabeledError::new(e.to_string()))?;
keyring::remove_tokens(&username).map_err(|e| LabeledError::new(e.to_string()))?;
Ok(Value::record(
record! {
@ -292,7 +290,11 @@ impl SimplePluginCommand for Verify {
"Control Center URL for remote verification",
None,
)
.switch("local", "Verify locally without contacting server", Some('l'))
.switch(
"local",
"Verify locally without contacting server",
Some('l'),
)
.category(Category::Custom("provisioning".into()))
}
@ -337,14 +339,13 @@ impl SimplePluginCommand for Verify {
t
} else {
let username = username_arg.unwrap_or_else(keyring::get_current_username);
keyring::get_access_token(&username)
.map_err(|e| LabeledError::new(e.to_string()))?
keyring::get_access_token(&username).map_err(|e| LabeledError::new(e.to_string()))?
};
if local_only {
// Local verification (no network)
let result = auth::verify_token_local(&token)
.map_err(|e| LabeledError::new(e.to_string()))?;
let result =
auth::verify_token_local(&token).map_err(|e| LabeledError::new(e.to_string()))?;
Ok(Value::record(
record! {
@ -448,8 +449,8 @@ impl SimplePluginCommand for Sessions {
// Get username and access token
let username = username_arg.unwrap_or_else(keyring::get_current_username);
let access_token = keyring::get_access_token(&username)
.map_err(|e| LabeledError::new(e.to_string()))?;
let access_token =
keyring::get_access_token(&username).map_err(|e| LabeledError::new(e.to_string()))?;
// List sessions from server
let sessions = helpers::list_sessions(&url, &access_token, active_only)
@ -536,8 +537,8 @@ impl SimplePluginCommand for MfaEnroll {
}
// Get access token
let access_token = keyring::get_access_token(&username)
.map_err(|e| LabeledError::new(e.to_string()))?;
let access_token =
keyring::get_access_token(&username).map_err(|e| LabeledError::new(e.to_string()))?;
// Send enrollment request
let response = helpers::send_mfa_enroll_request(&url, &access_token, &mfa_type)
@ -622,9 +623,7 @@ impl SimplePluginCommand for MfaVerify {
// Validate code format
if code.len() != 6 || !code.chars().all(|c| c.is_ascii_digit()) {
return Err(LabeledError::new(
"Code must be a 6-digit number",
));
return Err(LabeledError::new("Code must be a 6-digit number"));
}
let username = call
@ -635,8 +634,8 @@ impl SimplePluginCommand for MfaVerify {
.unwrap_or_else(|| DEFAULT_CONTROL_CENTER_URL.to_string());
// Get access token
let access_token = keyring::get_access_token(&username)
.map_err(|e| LabeledError::new(e.to_string()))?;
let access_token =
keyring::get_access_token(&username).map_err(|e| LabeledError::new(e.to_string()))?;
// Verify code
let valid = helpers::send_mfa_verify_request(&url, &access_token, &code)

View File

@ -117,10 +117,19 @@ fn test_auth_error_kind_display() {
"invalid credentials"
);
assert_eq!(AuthErrorKind::TokenExpired.to_string(), "token expired");
assert_eq!(AuthErrorKind::InvalidToken.to_string(), "invalid token format");
assert_eq!(AuthErrorKind::KeyringError.to_string(), "keyring operation failed");
assert_eq!(
AuthErrorKind::InvalidToken.to_string(),
"invalid token format"
);
assert_eq!(
AuthErrorKind::KeyringError.to_string(),
"keyring operation failed"
);
assert_eq!(AuthErrorKind::NetworkError.to_string(), "network error");
assert_eq!(AuthErrorKind::MfaFailed.to_string(), "MFA verification failed");
assert_eq!(
AuthErrorKind::MfaFailed.to_string(),
"MFA verification failed"
);
}
#[test]

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -10,14 +10,14 @@ keywords = [
homepage = "https://github.com/FMotalleb/nu_plugin_clipboard"
repository = "https://github.com/FMotalleb/nu_plugin_clipboard"
description = "A nushell plugin to copy text into clipboard or get text from it."
version = "0.109.1"
version = "0.111.0"
edition = "2024"
readme = "README.md"
[dependencies]
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
nu-json = "0.109.1"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
nu-json = "0.111.0"
[dependencies.arboard]
version = "3.6.1"

View File

@ -25,13 +25,15 @@ Try disabling the daemon mode, as mentioned in [#20](https://github.com/FMotalle
### Copying a string (supports only strings for now)
```bash
```nushell
bash
echo "test value" | clipboard copy
```
### Using clipboard content
```bash
```nushell
bash
clipboard paste | echo $in
```
@ -41,7 +43,8 @@ clipboard paste | echo $in
- When pasting, `clipboard paste` tries to parse JSON into a table or object.
- If parsing fails, the content is returned as a string.
```bash
```nushell
bash
$env | clipboard copy
clipboard paste
@ -55,7 +58,8 @@ clipboard paste
This method automatically handles dependencies and features:
```bash
```nushell
bash
git clone https://github.com/FMotalleb/nu_plugin_clipboard.git
nupm install --path nu_plugin_clipboard -f
```
@ -71,7 +75,8 @@ nupm install --path nu_plugin_clipboard -f
### 🛠️ Manual Compilation
```bash
```nushell
bash
git clone https://github.com/FMotalleb/nu_plugin_clipboard.git
cd nu_plugin_clipboard
cargo build -r
@ -80,7 +85,8 @@ plugin add target/release/nu_plugin_clipboard
### 📦 Install via Cargo (using git)
```bash
```rust
bash
cargo install --git https://github.com/FMotalleb/nu_plugin_clipboard.git
plugin add ~/.cargo/bin/nu_plugin_clipboard
```
@ -89,7 +95,8 @@ plugin add ~/.cargo/bin/nu_plugin_clipboard
- Since I live in Iran and crates.io won't let me update my packages like a normal person, most of the time crates.io is outdated.
```bash
```nushell
bash
cargo install nu_plugin_clipboard
plugin add ~/.cargo/bin/nu_plugin_clipboard
```

View File

@ -1,3 +1,3 @@
pub(crate) fn map_arboard_err_to_label(err: arboard::Error) -> nu_protocol::LabeledError {
nu_protocol::LabeledError::new(format!("Clipboard Error: {}", err.to_string()))
nu_protocol::LabeledError::new(format!("Clipboard Error: {}", err))
}

View File

@ -1,6 +1,6 @@
use std::{
env,
io::{stderr, stdout, Read, Write},
io::{Read, Write, stderr, stdout},
process::{Command, Stdio},
};

View File

@ -1,5 +1,5 @@
use crate::clipboard::clipboard::Clipboard;
use crate::ClipboardPlugins;
use crate::clipboard::clipboard::Clipboard;
use crate::{clipboard::clipboard::create_clipboard, utils::json};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{Category, IntoPipelineData, LabeledError, PipelineData, Signature, Type, Value};
@ -12,7 +12,7 @@ impl ClipboardCopy {
}
fn format_json(input: &Value) -> Result<String, LabeledError> {
let json_value =
json::value_to_json_value(&input).map(|v| nu_json::to_string_with_indent(&v, 4));
json::value_to_json_value(input).map(|v| nu_json::to_string_with_indent(&v, 4));
match json_value {
Ok(Ok(text)) => Ok(text.to_owned()), // Return the owned String
@ -62,9 +62,7 @@ impl PluginCommand for ClipboardCopy {
let value = input.into_value(call.head);
match value {
Ok(value) => {
if let Err(err) = Self::copy(engine, &value) {
return Err(err);
}
Self::copy(engine, &value)?;
Ok(value.into_pipeline_data())
}
Err(err) => Err(LabeledError::new(err.to_string())),

View File

@ -1,7 +1,7 @@
use crate::{
clipboard::clipboard::{create_clipboard, Clipboard},
utils::json::json_to_value,
ClipboardPlugins,
clipboard::clipboard::{Clipboard, create_clipboard},
utils::json::json_to_value,
};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{Category, IntoPipelineData, LabeledError, PipelineData, Type, Value};

View File

@ -4,13 +4,13 @@ pub mod utils;
use std::io;
#[cfg(target_os = "linux")]
use std::{
io::{stderr, stdout, Write},
io::{Write, stderr, stdout},
process::exit,
};
use crate::command::copy::ClipboardCopy;
use crate::command::paste::ClipboardPaste;
use clipboard::clipboard::{create_clipboard, CheckResult, Clipboard};
use clipboard::clipboard::{CheckResult, Clipboard, create_clipboard};
use nu_plugin::PluginCommand;
pub struct ClipboardPlugins;
@ -30,10 +30,13 @@ impl nu_plugin::Plugin for ClipboardPlugins {
fn main() -> Result<(), io::Error> {
match create_clipboard(None).pre_execute_check() {
CheckResult::Continue => Ok(nu_plugin::serve_plugin(
&mut ClipboardPlugins {},
nu_plugin::MsgPackSerializer {},
)),
CheckResult::Continue => {
nu_plugin::serve_plugin(
&ClipboardPlugins {},
nu_plugin::MsgPackSerializer {},
);
Ok(())
},
#[cfg(target_os = "linux")]
CheckResult::Exit(message, code) => {
if code != 0 {

View File

@ -38,12 +38,6 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -53,6 +47,56 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
@ -227,7 +271,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"rustc-hash 1.1.0",
"shlex",
"syn",
]
@ -370,16 +414,15 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.39"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"android-tzdata",
"iana-time-zone",
"num-traits",
"pure-rust-locales",
"serde",
"windows-targets 0.52.6",
"windows-link 0.2.1",
]
[[package]]
@ -402,6 +445,40 @@ dependencies = [
"libloading",
]
[[package]]
name = "clap"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
]
[[package]]
name = "clap_lex"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "concurrent-queue"
version = "2.5.0"
@ -540,6 +617,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "doctest-file"
version = "1.0.0"
@ -637,9 +725,9 @@ dependencies = [
[[package]]
name = "fancy-regex"
version = "0.16.2"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
dependencies = [
"bit-set",
"regex-automata",
@ -663,10 +751,55 @@ dependencies = [
]
[[package]]
name = "foldhash"
version = "0.1.4"
name = "fluent"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0d2fde1f7b3d48b8395d5f2de76c18a528bd6a9cdde438df747bfcba3e05d6f"
checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477"
dependencies = [
"fluent-bundle",
"unic-langid",
]
[[package]]
name = "fluent-bundle"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4"
dependencies = [
"fluent-langneg",
"fluent-syntax",
"intl-memoizer",
"intl_pluralrules",
"rustc-hash 2.1.1",
"self_cell",
"smallvec",
"unic-langid",
]
[[package]]
name = "fluent-langneg"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0"
dependencies = [
"unic-langid",
]
[[package]]
name = "fluent-syntax"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198"
dependencies = [
"memchr",
"thiserror 2.0.18",
]
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "futures-core"
@ -724,9 +857,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
version = "0.15.2"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
@ -776,9 +909,9 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.11.4"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown",
@ -786,9 +919,9 @@ dependencies = [
[[package]]
name = "interprocess"
version = "2.2.2"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "894148491d817cb36b6f778017b8ac46b17408d522dd90f539d677ea938362eb"
checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69"
dependencies = [
"doctest-file",
"libc",
@ -797,6 +930,25 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "intl-memoizer"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f"
dependencies = [
"type-map",
"unic-langid",
]
[[package]]
name = "intl_pluralrules"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
dependencies = [
"unic-langid",
]
[[package]]
name = "inventory"
version = "0.3.19"
@ -812,6 +964,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.13.0"
@ -860,9 +1018,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.175"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libloading"
@ -931,9 +1089,9 @@ checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f"
[[package]]
name = "lru"
version = "0.12.5"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
"hashbrown",
]
@ -963,12 +1121,9 @@ dependencies = [
[[package]]
name = "mach2"
version = "0.4.2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709"
dependencies = [
"libc",
]
checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
[[package]]
name = "malloc_buf"
@ -981,9 +1136,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.7.4"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "memoffset"
@ -1008,7 +1163,7 @@ dependencies = [
"supports-unicode",
"terminal_size",
"textwrap",
"unicode-width",
"unicode-width 0.1.14",
]
[[package]]
@ -1118,9 +1273,9 @@ dependencies = [
[[package]]
name = "nu-derive-value"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465d2d3ada6004cb6689f269a08c70ba81056231e2b5392d1e0ccf5825f81cb"
checksum = "d71958b54c367bda033f7dcc4a73b61972fb52323f71a1e3533e290fa67148d1"
dependencies = [
"heck",
"proc-macro-error2",
@ -1131,9 +1286,9 @@ dependencies = [
[[package]]
name = "nu-engine"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3b777faf7c5180fe5d7f67d83c44fd14138d91f2938a36494ed6ac66b7160f3"
checksum = "d41b3e3e2d25c30741a0761856258e22624c0d60064e4f0e12f86202a451d492"
dependencies = [
"fancy-regex",
"log",
@ -1146,25 +1301,25 @@ dependencies = [
[[package]]
name = "nu-experimental"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73dd212a1afdad646a38c00579a0988264880aeb97fee820b349a28cdcc04df2"
checksum = "f328fa0531bdf49c2dc0312b40cb780e3d74e0d3dbb15d508469a5ae4cfd8d8f"
dependencies = [
"itertools 0.14.0",
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
name = "nu-glob"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15aa2c17078926f14e393b4b708e69f228cb6fd4c81136839bde82772bdde1b5"
checksum = "01ee787f61353c9c90581ddf4c0602a07b991cdd06c97dac8b6d323a1a52c43a"
[[package]]
name = "nu-path"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde9d8ba26f62c07176c0237a36f38ce964ab3a0dcfb6aab1feea7515d1c6594"
checksum = "c01d110cb931acf56237ce572e5b156e8e1134227c90deeffb92eedda9482c23"
dependencies = [
"dirs",
"omnipath",
@ -1174,9 +1329,9 @@ dependencies = [
[[package]]
name = "nu-plugin"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea1fbfd41b2f5c967675fc948831e03be67d91c6b8e18a60f3445113fe6548c"
checksum = "c322531b1a7d6338c5ead1f454294f46babf8c99cd4716311cab1e88ba52b154"
dependencies = [
"log",
"nix 0.30.1",
@ -1185,14 +1340,14 @@ dependencies = [
"nu-plugin-protocol",
"nu-protocol",
"nu-utils",
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
name = "nu-plugin-core"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2410648c2c38cf9359595ffcf281d9d60a81c0580ff07f7c7d42bed414f3a1"
checksum = "38ee792aeb0d37e0ed55ca4304e434eece497914e27ae42616a8bb973f5d2720"
dependencies = [
"interprocess",
"log",
@ -1206,9 +1361,9 @@ dependencies = [
[[package]]
name = "nu-plugin-protocol"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27de26da922261dff8103a811879228c55749a1b7b0e573b639c609a0651a01e"
checksum = "7725f341428db16dbef4392970de32705abc77ee80a902572c8da811dade3564"
dependencies = [
"nu-protocol",
"nu-utils",
@ -1220,9 +1375,9 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038943300ca9de0924fef1c795a7dd16ffc67105629477cf163e8ee6bad95ea6"
checksum = "f1c0e58cbeb46cbfd40156e6f4b9f90e4a77e774ca863fa158867a4726aab1d1"
dependencies = [
"brotli",
"bytes",
@ -1251,7 +1406,7 @@ dependencies = [
"serde_json",
"strum",
"strum_macros",
"thiserror 2.0.12",
"thiserror 2.0.18",
"typetag",
"web-time",
"windows 0.62.2",
@ -1260,9 +1415,9 @@ dependencies = [
[[package]]
name = "nu-system"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46be734cc9b19e09a9665769e14360e13e6978490056ba5c8bfad7dd0537ea83"
checksum = "62fe7847b65edbe362a0fcb67dedfab9fd7370e89c0313f7cb7d0a7ab8f9834b"
dependencies = [
"chrono",
"itertools 0.14.0",
@ -1274,15 +1429,16 @@ dependencies = [
"ntapi",
"procfs",
"sysinfo",
"uucore",
"web-time",
"windows 0.62.2",
]
[[package]]
name = "nu-utils"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f8eb43c29cc5bce85f87defdadc2cca964fa434d808af37036a7cb78f3c68e9"
checksum = "df85a8a4bb28c84d5f7c096c02c859ac454dfac59fd0296ab5eb6ed86619219e"
dependencies = [
"byteyarn",
"crossterm",
@ -1303,7 +1459,7 @@ dependencies = [
[[package]]
name = "nu_plugin_desktop_notifications"
version = "0.109.1"
version = "0.111.0"
dependencies = [
"notify-rust",
"nu-plugin",
@ -1357,18 +1513,18 @@ dependencies = [
[[package]]
name = "objc2-core-foundation"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
]
[[package]]
name = "objc2-io-kit"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
dependencies = [
"libc",
"objc2-core-foundation",
@ -1395,6 +1551,12 @@ version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "option-ext"
version = "0.2.0"
@ -1411,6 +1573,15 @@ dependencies = [
"pin-project-lite",
]
[[package]]
name = "os_display"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad5fd71b79026fb918650dde6d125000a233764f1c2f1659a1c71118e33ea08f"
dependencies = [
"unicode-width 0.2.2",
]
[[package]]
name = "os_pipe"
version = "1.2.1"
@ -1542,23 +1713,22 @@ dependencies = [
[[package]]
name = "procfs"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7"
dependencies = [
"bitflags",
"chrono",
"flate2",
"hex",
"procfs-core",
"rustix 0.38.44",
"rustix 1.1.2",
]
[[package]]
name = "procfs-core"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405"
dependencies = [
"bitflags",
"chrono",
@ -1567,9 +1737,9 @@ dependencies = [
[[package]]
name = "pure-rust-locales"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a"
checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d"
[[package]]
name = "pwd"
@ -1633,23 +1803,23 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.15",
"libredox",
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
name = "ref-cast"
version = "1.0.23"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.23"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
@ -1698,11 +1868,10 @@ dependencies = [
[[package]]
name = "rmp-serde"
version = "1.3.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
dependencies = [
"byteorder",
"rmp",
"serde",
]
@ -1713,6 +1882,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "0.38.44"
@ -1757,6 +1932,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "self_cell"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
[[package]]
name = "semver"
version = "1.0.25"
@ -1765,18 +1946,28 @@ checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
[[package]]
name = "serde"
version = "1.0.217"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
]
[[package]]
name = "serde_core"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.217"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@ -1785,14 +1976,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.138"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@ -1873,10 +2065,16 @@ dependencies = [
]
[[package]]
name = "strum"
version = "0.26.3"
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
[[package]]
name = "strum_macros"
@ -1933,16 +2131,16 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.37.2"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f"
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows 0.61.1",
"windows 0.62.2",
]
[[package]]
@ -1952,7 +2150,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9"
dependencies = [
"quick-xml",
"thiserror 2.0.12",
"thiserror 2.0.18",
"windows 0.61.1",
"windows-version",
]
@ -1988,7 +2186,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9"
dependencies = [
"unicode-linebreak",
"unicode-width",
"unicode-width 0.1.14",
]
[[package]]
@ -2002,11 +2200,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.12"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.12",
"thiserror-impl 2.0.18",
]
[[package]]
@ -2022,9 +2220,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.12"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
@ -2050,6 +2248,17 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"serde_core",
"zerovec",
]
[[package]]
name = "toml_datetime"
version = "0.6.8"
@ -2098,6 +2307,15 @@ dependencies = [
"once_cell",
]
[[package]]
name = "type-map"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
dependencies = [
"rustc-hash 2.1.1",
]
[[package]]
name = "typeid"
version = "1.0.2"
@ -2140,10 +2358,28 @@ dependencies = [
]
[[package]]
name = "unicase"
version = "2.8.1"
name = "unic-langid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05"
dependencies = [
"unic-langid-impl",
]
[[package]]
name = "unic-langid-impl"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658"
dependencies = [
"tinystr",
]
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
@ -2169,6 +2405,47 @@ version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uucore"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b157ba598d7f7ed06f6dbc62999edb9d730b4d3fb58e503d8ad6d5fbe1e04391"
dependencies = [
"clap",
"fluent",
"fluent-bundle",
"fluent-syntax",
"libc",
"nix 0.30.1",
"os_display",
"thiserror 2.0.18",
"unic-langid",
"uucore_procs",
"wild",
]
[[package]]
name = "uucore_procs"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daa291a52608ac5a2f8539e119666e021baa6b8c01f22f02ed201bbae54cbbc0"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "vte"
version = "0.14.1"
@ -2267,6 +2544,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311"
[[package]]
name = "wild"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1"
dependencies = [
"glob",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -2763,6 +3049,28 @@ dependencies = [
"syn",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"serde",
"zerofrom",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zvariant"
version = "5.4.0"

View File

@ -1,6 +1,6 @@
[dependencies]
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
[dependencies.notify-rust]
version = "4.11.7"
@ -20,4 +20,4 @@ license = "MIT"
name = "nu_plugin_desktop_notifications"
readme = "README.md"
repository = "https://github.com/FMotalleb/nu_plugin_desktop_notifications"
version = "0.109.1"
version = "0.111.0"

View File

@ -17,7 +17,8 @@ A [Nushell](https://www.nushell.sh/) plugin for sending desktop notifications us
### **Sending a Notification**
```bash
```nushell
bash
notify -t "Test notification body" --summary "Test title"
```
@ -40,7 +41,8 @@ Send a notification after a task completes, displaying the elapsed time:
![image](https://github.com/FMotalleb/nu_plugin_desktop_notifications/assets/30149519/a4fbc2a9-6537-4d18-8d98-e55ebcd6b0bd)
```bash
```nushell
bash
def "notify on done" [
task: closure
] {
@ -62,14 +64,16 @@ notify on done { port scan 8.8.8.8 53 }
### 🚀 Recommended: Using [nupm](https://github.com/nushell/nupm)
```bash
```nushell
bash
git clone https://github.com/FMotalleb/nu_plugin_desktop_notifications.git
nupm install --path nu_plugin_desktop_notifications -f
```
### 🛠️ Manual Compilation
```bash
```nushell
bash
git clone https://github.com/FMotalleb/nu_plugin_desktop_notifications.git
cd nu_plugin_desktop_notifications
cargo build -r
@ -78,7 +82,8 @@ register target/release/nu_plugin_desktop_notifications
### 📦 Install via Cargo (using git)
```bash
```rust
bash
cargo install --git https://github.com/FMotalleb/nu_plugin_desktop_notifications.git
register ~/.cargo/bin/nu_plugin_desktop_notifications
```
@ -87,7 +92,8 @@ register ~/.cargo/bin/nu_plugin_desktop_notifications
>
> _Since I live in Iran and crates.io often restricts package updates, the version there might be outdated._
```bash
```nushell
bash
cargo install nu_plugin_desktop_notifications
register ~/.cargo/bin/nu_plugin_desktop_notifications
```

View File

@ -1,4 +1,4 @@
use nu_plugin::{serve_plugin, Plugin};
use nu_plugin::{Plugin, serve_plugin};
use crate::notify::NotifyCommand;
mod notify;
@ -15,5 +15,5 @@ impl Plugin for NotifyPlugin {
}
fn main() {
serve_plugin(&mut NotifyPlugin {}, nu_plugin::MsgPackSerializer {})
serve_plugin(&NotifyPlugin {}, nu_plugin::MsgPackSerializer {})
}

View File

@ -107,14 +107,11 @@ impl SimplePluginCommand for NotifyCommand {
}
if let Some(duration_value) = call.get_flag_value("timeout") {
match duration_value.as_duration() {
Ok(timeout) => {
if let Ok(nanos) = timeout.try_into() {
let duration = Timeout::from(Duration::from_nanos(nanos));
notification.timeout(duration);
}
if let Ok(timeout) = duration_value.as_duration() {
if let Ok(nanos) = timeout.try_into() {
let duration = Timeout::from(Duration::from_nanos(nanos));
notification.timeout(duration);
}
Err(_) => {}
}
}

View File

@ -47,6 +47,56 @@ dependencies = [
"libc",
]
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.0",
]
[[package]]
name = "arrayvec"
version = "0.7.6"
@ -197,9 +247,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.42"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "145052bdd345b87320e369255277e3fb5152762ad123a901ef5c262dd38fe8d2"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"iana-time-zone",
"num-traits",
@ -228,6 +278,40 @@ dependencies = [
"libloading",
]
[[package]]
name = "clap"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
version = "4.5.60"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
]
[[package]]
name = "clap_lex"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "convert_case"
version = "0.7.1"
@ -264,7 +348,7 @@ dependencies = [
"document-features",
"mio",
"parking_lot",
"rustix 1.1.2",
"rustix",
"signal-hook",
"signal-hook-mio",
"winapi",
@ -382,9 +466,9 @@ dependencies = [
[[package]]
name = "fancy-regex"
version = "0.16.2"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
dependencies = [
"bit-set",
"regex-automata",
@ -455,14 +539,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198"
dependencies = [
"memchr",
"thiserror 2.0.16",
"thiserror 2.0.18",
]
[[package]]
name = "foldhash"
version = "0.1.5"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "getrandom"
@ -495,21 +579,15 @@ checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280"
[[package]]
name = "hashbrown"
version = "0.15.5"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
"foldhash",
]
[[package]]
name = "hashbrown"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d"
[[package]]
name = "heck"
version = "0.5.0"
@ -534,7 +612,7 @@ dependencies = [
"js-sys",
"log",
"wasm-bindgen",
"windows-core 0.62.2",
"windows-core",
]
[[package]]
@ -548,19 +626,19 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.12.0"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.16.0",
"hashbrown",
]
[[package]]
name = "interprocess"
version = "2.2.3"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d"
checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69"
dependencies = [
"doctest-file",
"libc",
@ -603,6 +681,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7655c9839580ee829dfacba1d1278c2b7883e50a277ff7541299489d6bdfdc45"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]]
name = "itertools"
version = "0.13.0"
@ -651,9 +735,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.175"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libloading"
@ -686,12 +770,6 @@ dependencies = [
"libc",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.11.0"
@ -722,11 +800,11 @@ checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432"
[[package]]
name = "lru"
version = "0.12.5"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
"hashbrown 0.15.5",
"hashbrown",
]
[[package]]
@ -741,18 +819,15 @@ dependencies = [
[[package]]
name = "mach2"
version = "0.4.3"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
dependencies = [
"libc",
]
checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
[[package]]
name = "memchr"
version = "2.7.5"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "miette"
@ -851,9 +926,9 @@ dependencies = [
[[package]]
name = "nu-derive-value"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465d2d3ada6004cb6689f269a08c70ba81056231e2b5392d1e0ccf5825f81cb"
checksum = "d71958b54c367bda033f7dcc4a73b61972fb52323f71a1e3533e290fa67148d1"
dependencies = [
"heck",
"proc-macro-error2",
@ -864,9 +939,9 @@ dependencies = [
[[package]]
name = "nu-engine"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3b777faf7c5180fe5d7f67d83c44fd14138d91f2938a36494ed6ac66b7160f3"
checksum = "d41b3e3e2d25c30741a0761856258e22624c0d60064e4f0e12f86202a451d492"
dependencies = [
"fancy-regex",
"log",
@ -879,25 +954,25 @@ dependencies = [
[[package]]
name = "nu-experimental"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73dd212a1afdad646a38c00579a0988264880aeb97fee820b349a28cdcc04df2"
checksum = "f328fa0531bdf49c2dc0312b40cb780e3d74e0d3dbb15d508469a5ae4cfd8d8f"
dependencies = [
"itertools 0.14.0",
"thiserror 2.0.16",
"thiserror 2.0.18",
]
[[package]]
name = "nu-glob"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15aa2c17078926f14e393b4b708e69f228cb6fd4c81136839bde82772bdde1b5"
checksum = "01ee787f61353c9c90581ddf4c0602a07b991cdd06c97dac8b6d323a1a52c43a"
[[package]]
name = "nu-path"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde9d8ba26f62c07176c0237a36f38ce964ab3a0dcfb6aab1feea7515d1c6594"
checksum = "c01d110cb931acf56237ce572e5b156e8e1134227c90deeffb92eedda9482c23"
dependencies = [
"dirs",
"omnipath",
@ -907,9 +982,9 @@ dependencies = [
[[package]]
name = "nu-plugin"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea1fbfd41b2f5c967675fc948831e03be67d91c6b8e18a60f3445113fe6548c"
checksum = "c322531b1a7d6338c5ead1f454294f46babf8c99cd4716311cab1e88ba52b154"
dependencies = [
"log",
"nix",
@ -918,14 +993,14 @@ dependencies = [
"nu-plugin-protocol",
"nu-protocol",
"nu-utils",
"thiserror 2.0.16",
"thiserror 2.0.18",
]
[[package]]
name = "nu-plugin-core"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2410648c2c38cf9359595ffcf281d9d60a81c0580ff07f7c7d42bed414f3a1"
checksum = "38ee792aeb0d37e0ed55ca4304e434eece497914e27ae42616a8bb973f5d2720"
dependencies = [
"interprocess",
"log",
@ -934,14 +1009,14 @@ dependencies = [
"rmp-serde",
"serde",
"serde_json",
"windows 0.62.2",
"windows",
]
[[package]]
name = "nu-plugin-protocol"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27de26da922261dff8103a811879228c55749a1b7b0e573b639c609a0651a01e"
checksum = "7725f341428db16dbef4392970de32705abc77ee80a902572c8da811dade3564"
dependencies = [
"nu-protocol",
"nu-utils",
@ -953,9 +1028,9 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038943300ca9de0924fef1c795a7dd16ffc67105629477cf163e8ee6bad95ea6"
checksum = "f1c0e58cbeb46cbfd40156e6f4b9f90e4a77e774ca863fa158867a4726aab1d1"
dependencies = [
"brotli",
"bytes",
@ -984,18 +1059,18 @@ dependencies = [
"serde_json",
"strum",
"strum_macros",
"thiserror 2.0.16",
"thiserror 2.0.18",
"typetag",
"web-time",
"windows 0.62.2",
"windows",
"windows-sys 0.61.0",
]
[[package]]
name = "nu-system"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46be734cc9b19e09a9665769e14360e13e6978490056ba5c8bfad7dd0537ea83"
checksum = "62fe7847b65edbe362a0fcb67dedfab9fd7370e89c0313f7cb7d0a7ab8f9834b"
dependencies = [
"chrono",
"itertools 0.14.0",
@ -1007,15 +1082,16 @@ dependencies = [
"ntapi",
"procfs",
"sysinfo",
"uucore",
"web-time",
"windows 0.62.2",
"windows",
]
[[package]]
name = "nu-utils"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f8eb43c29cc5bce85f87defdadc2cca964fa434d808af37036a7cb78f3c68e9"
checksum = "df85a8a4bb28c84d5f7c096c02c859ac454dfac59fd0296ab5eb6ed86619219e"
dependencies = [
"byteyarn",
"crossterm",
@ -1036,7 +1112,7 @@ dependencies = [
[[package]]
name = "nu_plugin_fluent"
version = "0.109.1"
version = "0.111.0"
dependencies = [
"fluent",
"fluent-bundle",
@ -1047,7 +1123,7 @@ dependencies = [
"serde",
"serde_json",
"tempfile",
"thiserror 2.0.16",
"thiserror 2.0.18",
"unic-langid",
]
@ -1072,18 +1148,18 @@ dependencies = [
[[package]]
name = "objc2-core-foundation"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
]
[[package]]
name = "objc2-io-kit"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
dependencies = [
"libc",
"objc2-core-foundation",
@ -1101,12 +1177,27 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
name = "option-ext"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "os_display"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad5fd71b79026fb918650dde6d125000a233764f1c2f1659a1c71118e33ea08f"
dependencies = [
"unicode-width 0.2.1",
]
[[package]]
name = "os_pipe"
version = "1.2.2"
@ -1185,23 +1276,22 @@ dependencies = [
[[package]]
name = "procfs"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7"
dependencies = [
"bitflags",
"chrono",
"flate2",
"hex",
"procfs-core",
"rustix 0.38.44",
"rustix",
]
[[package]]
name = "procfs-core"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405"
dependencies = [
"bitflags",
"chrono",
@ -1210,9 +1300,9 @@ dependencies = [
[[package]]
name = "pure-rust-locales"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a"
checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d"
[[package]]
name = "pwd"
@ -1262,23 +1352,23 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
"thiserror 2.0.16",
"thiserror 2.0.18",
]
[[package]]
name = "ref-cast"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
@ -1327,11 +1417,10 @@ dependencies = [
[[package]]
name = "rmp-serde"
version = "1.3.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
dependencies = [
"byteorder",
"rmp",
"serde",
]
@ -1348,19 +1437,6 @@ version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
version = "0.38.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
[[package]]
name = "rustix"
version = "1.1.2"
@ -1370,7 +1446,7 @@ dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.11.0",
"linux-raw-sys",
"windows-sys 0.61.0",
]
@ -1406,9 +1482,9 @@ checksum = "d767eb0aabc880b29956c35734170f26ed551a859dbd361d140cdbeca61ab1e2"
[[package]]
name = "serde"
version = "1.0.225"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd6c24dee235d0da097043389623fb913daddf92c76e9f5a1db88607a0bcbd1d"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [
"serde_core",
"serde_derive",
@ -1416,18 +1492,18 @@ dependencies = [
[[package]]
name = "serde_core"
version = "1.0.225"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "659356f9a0cb1e529b24c01e43ad2bdf520ec4ceaf83047b83ddcc2251f96383"
checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.225"
version = "1.0.228"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ea936adf78b1f766949a4977b91d2f5595825bd6ec079aa9543ad2685fc4516"
checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79"
dependencies = [
"proc-macro2",
"quote",
@ -1436,15 +1512,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.145"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@ -1499,10 +1575,16 @@ dependencies = [
]
[[package]]
name = "strum"
version = "0.26.3"
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
[[package]]
name = "strum_macros"
@ -1559,16 +1641,16 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.37.2"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f"
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows 0.61.3",
"windows",
]
[[package]]
@ -1580,7 +1662,7 @@ dependencies = [
"fastrand",
"getrandom 0.3.3",
"once_cell",
"rustix 1.1.2",
"rustix",
"windows-sys 0.61.0",
]
@ -1590,7 +1672,7 @@ version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b8cb979cb11c32ce1603f8137b22262a9d131aaa5c37b5678025f22b8becd0"
dependencies = [
"rustix 1.1.2",
"rustix",
"windows-sys 0.60.2",
]
@ -1615,11 +1697,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.16"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.16",
"thiserror-impl 2.0.18",
]
[[package]]
@ -1635,9 +1717,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.16"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
@ -1713,9 +1795,9 @@ dependencies = [
[[package]]
name = "unicase"
version = "2.8.1"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
@ -1747,6 +1829,41 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uucore"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b157ba598d7f7ed06f6dbc62999edb9d730b4d3fb58e503d8ad6d5fbe1e04391"
dependencies = [
"clap",
"fluent",
"fluent-bundle",
"fluent-syntax",
"libc",
"nix",
"os_display",
"thiserror 2.0.18",
"unic-langid",
"uucore_procs",
"wild",
]
[[package]]
name = "uucore_procs"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daa291a52608ac5a2f8539e119666e021baa6b8c01f22f02ed201bbae54cbbc0"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "vte"
version = "0.14.1"
@ -1855,6 +1972,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
[[package]]
name = "wild"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1"
dependencies = [
"glob",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -1877,38 +2003,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections 0.2.0",
"windows-core 0.61.2",
"windows-future 0.2.1",
"windows-link 0.1.3",
"windows-numerics 0.2.0",
]
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections 0.3.2",
"windows-core 0.62.2",
"windows-future 0.3.2",
"windows-numerics 0.3.1",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.2",
"windows-collections",
"windows-core",
"windows-future",
"windows-numerics",
]
[[package]]
@ -1917,20 +2021,7 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
dependencies = [
"windows-core 0.62.2",
]
[[package]]
name = "windows-core"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3"
dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.1.3",
"windows-result 0.3.4",
"windows-strings 0.4.2",
"windows-core",
]
[[package]]
@ -1942,19 +2033,8 @@ dependencies = [
"windows-implement",
"windows-interface",
"windows-link 0.2.1",
"windows-result 0.4.1",
"windows-strings 0.5.1",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-threading 0.1.0",
"windows-result",
"windows-strings",
]
[[package]]
@ -1963,9 +2043,9 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core 0.62.2",
"windows-core",
"windows-link 0.2.1",
"windows-threading 0.2.1",
"windows-threading",
]
[[package]]
@ -2002,35 +2082,16 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
]
[[package]]
name = "windows-numerics"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
dependencies = [
"windows-core 0.62.2",
"windows-core",
"windows-link 0.2.1",
]
[[package]]
name = "windows-result"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-result"
version = "0.4.1"
@ -2040,15 +2101,6 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "windows-strings"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-strings"
version = "0.5.1"
@ -2127,15 +2179,6 @@ dependencies = [
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
@ -2282,3 +2325,9 @@ checksum = "e7aa2bd55086f1ab526693ecbe444205da57e25f4489879da80635a46d90e73b"
dependencies = [
"zerofrom",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"

View File

@ -1,6 +1,6 @@
[package]
name = "nu_plugin_fluent"
version = "0.109.1"
version = "0.111.0"
edition = "2021"
description = "Nushell plugin for Fluent i18n integration"
authors = ["Jesús Pérex <jpl@jesusperez.com>"]
@ -19,8 +19,8 @@ categories = [
]
[dependencies]
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
serde_json = "1.0"
fluent = "0.17"
fluent-bundle = "0.16"

File diff suppressed because one or more lines are too long

View File

@ -1,10 +1,8 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, Signature, Span, SyntaxShape, Type, Value, record,
};
use fluent::{FluentBundle, FluentResource};
use unic_langid::LanguageIdentifier;
use crate::FluentPlugin;
use fluent::{FluentBundle, FluentResource};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{record, Category, LabeledError, Signature, Span, SyntaxShape, Type, Value};
use unic_langid::LanguageIdentifier;
pub struct CreateBundle;
@ -37,7 +35,11 @@ impl SimplePluginCommand for CreateBundle {
"Fallback locales in order",
Some('f'),
)
.switch("override", "Allow page files to override global messages", Some('o'))
.switch(
"override",
"Allow page files to override global messages",
Some('o'),
)
.category(Category::Strings)
}
@ -70,16 +72,24 @@ impl SimplePluginCommand for CreateBundle {
let locale_code: String = call.req(0)?;
// Parse locale
let locale: LanguageIdentifier = locale_code.parse()
.map_err(|e| LabeledError::new("Invalid locale").with_label(format!("Invalid locale '{}': {}", locale_code, e), call.head))?;
let locale: LanguageIdentifier = locale_code.parse().map_err(|e| {
LabeledError::new("Invalid locale").with_label(
format!("Invalid locale '{}': {}", locale_code, e),
call.head,
)
})?;
// Create fallback locales
let mut locales = vec![locale.clone()];
if let Some(fallback_value) = call.get_flag("fallback")? {
let fallback_codes = extract_string_list(fallback_value)?;
for code in fallback_codes {
let fallback_locale: LanguageIdentifier = code.parse()
.map_err(|e| LabeledError::new("Invalid fallback locale").with_label(format!("Invalid fallback locale '{}': {}", code, e), call.head))?;
let fallback_locale: LanguageIdentifier = code.parse().map_err(|e| {
LabeledError::new("Invalid fallback locale").with_label(
format!("Invalid fallback locale '{}': {}", code, e),
call.head,
)
})?;
locales.push(fallback_locale);
}
}
@ -115,12 +125,12 @@ impl SimplePluginCommand for CreateBundle {
fn extract_string_list(value: Value) -> Result<Vec<String>, LabeledError> {
match value {
Value::List { vals, .. } => {
vals.iter()
.map(|v| value_to_string(v))
.collect::<Result<Vec<_>, _>>()
}
_ => Err(LabeledError::new("Invalid list").with_label("Must be a list of strings", nu_protocol::Span::unknown())),
Value::List { vals, .. } => vals
.iter()
.map(|v| value_to_string(v))
.collect::<Result<Vec<_>, _>>(),
_ => Err(LabeledError::new("Invalid list")
.with_label("Must be a list of strings", nu_protocol::Span::unknown())),
}
}
@ -134,24 +144,32 @@ fn load_files_to_bundle(
source: &str,
) -> Result<(), LabeledError> {
for file_path in files {
let content = std::fs::read_to_string(file_path)
.map_err(|e| LabeledError::new("Read error").with_label(format!("Failed to read '{}': {}", file_path, e), nu_protocol::Span::unknown()))?;
let content = std::fs::read_to_string(file_path).map_err(|e| {
LabeledError::new("Read error").with_label(
format!("Failed to read '{}': {}", file_path, e),
nu_protocol::Span::unknown(),
)
})?;
let resource = FluentResource::try_new(content)
.map_err(|e| LabeledError::new("Invalid FTL").with_label(format!("Invalid FTL in '{}': {:?}", file_path, e), nu_protocol::Span::unknown()))?;
let resource = FluentResource::try_new(content).map_err(|e| {
LabeledError::new("Invalid FTL").with_label(
format!("Invalid FTL in '{}': {:?}", file_path, e),
nu_protocol::Span::unknown(),
)
})?;
bundle.add_resource(resource)
.map_err(|errors| {
let error_msgs: Vec<String> = errors.iter()
.map(|e| format!("{:?}", e))
.collect();
LabeledError::new("Load error").with_label(format!(
bundle.add_resource(resource).map_err(|errors| {
let error_msgs: Vec<String> = errors.iter().map(|e| format!("{:?}", e)).collect();
LabeledError::new("Load error").with_label(
format!(
"Failed to load {} file '{}': {}",
source,
file_path,
error_msgs.join(", ")
), nu_protocol::Span::unknown())
})?;
),
nu_protocol::Span::unknown(),
)
})?;
}
Ok(())
}
@ -197,7 +215,7 @@ fn value_to_string(value: &Value) -> Result<String, LabeledError> {
Value::Bool { val, .. } => Ok(val.to_string()),
_ => Err(LabeledError::new("Type conversion error").with_label(
format!("Cannot convert {:?} to string", value.get_type()),
nu_protocol::Span::unknown()
nu_protocol::Span::unknown(),
)),
}
}

View File

@ -1,6 +1,6 @@
use crate::FluentPlugin;
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Type, Value};
use crate::FluentPlugin;
pub struct ExtractMessages;
@ -14,7 +14,11 @@ impl SimplePluginCommand for ExtractMessages {
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self))
.input_output_type(Type::Any, Type::List(Box::new(Type::String)))
.required("file", SyntaxShape::Filepath, "FTL file to extract messages from")
.required(
"file",
SyntaxShape::Filepath,
"FTL file to extract messages from",
)
.category(Category::Strings)
}
@ -23,13 +27,11 @@ impl SimplePluginCommand for ExtractMessages {
}
fn examples(&self) -> Vec<nu_protocol::Example<'_>> {
vec![
nu_protocol::Example {
description: "Extract message IDs from an FTL file",
example: "fluent-extract locales/en-US/main.ftl",
result: None,
},
]
vec![nu_protocol::Example {
description: "Extract message IDs from an FTL file",
example: "fluent-extract locales/en-US/main.ftl",
result: None,
}]
}
fn run(

View File

@ -1,7 +1,7 @@
use crate::FluentPlugin;
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Type, Value};
use std::fs;
use crate::FluentPlugin;
pub struct ListLocales;
@ -15,7 +15,11 @@ impl SimplePluginCommand for ListLocales {
fn signature(&self) -> Signature {
Signature::build(PluginCommand::name(self))
.input_output_type(Type::Any, Type::List(Box::new(Type::String)))
.required("directory", SyntaxShape::Directory, "Directory containing locale folders")
.required(
"directory",
SyntaxShape::Directory,
"Directory containing locale folders",
)
.category(Category::Strings)
}
@ -24,13 +28,11 @@ impl SimplePluginCommand for ListLocales {
}
fn examples(&self) -> Vec<nu_protocol::Example<'_>> {
vec![
nu_protocol::Example {
description: "List available locales",
example: "fluent-list-locales ./locales",
result: None,
},
]
vec![nu_protocol::Example {
description: "List available locales",
example: "fluent-list-locales ./locales",
result: None,
}]
}
fn run(
@ -42,17 +44,27 @@ impl SimplePluginCommand for ListLocales {
) -> Result<Value, LabeledError> {
let directory: String = call.req(0)?;
let entries = fs::read_dir(&directory)
.map_err(|e| LabeledError::new("Read error").with_label(format!("Failed to read directory '{}': {}", directory, e), call.head))?;
let entries = fs::read_dir(&directory).map_err(|e| {
LabeledError::new("Read error").with_label(
format!("Failed to read directory '{}': {}", directory, e),
call.head,
)
})?;
let mut locales = Vec::new();
for entry in entries {
let entry = entry
.map_err(|e| LabeledError::new("Read error").with_label(format!("Failed to read directory entry: {}", e), call.head))?;
let entry = entry.map_err(|e| {
LabeledError::new("Read error")
.with_label(format!("Failed to read directory entry: {}", e), call.head)
})?;
if entry.file_type()
.map_err(|e| LabeledError::new("File type error").with_label(format!("Failed to get file type: {}", e), call.head))?
if entry
.file_type()
.map_err(|e| {
LabeledError::new("File type error")
.with_label(format!("Failed to get file type: {}", e), call.head)
})?
.is_dir()
{
if let Some(name) = entry.file_name().to_str() {

View File

@ -1,10 +1,8 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, Signature, SyntaxShape, Type, Value
};
use fluent::{FluentBundle, FluentResource, FluentArgs, FluentValue};
use unic_langid::LanguageIdentifier;
use crate::FluentPlugin;
use fluent::{FluentArgs, FluentBundle, FluentResource, FluentValue};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Type, Value};
use unic_langid::LanguageIdentifier;
pub struct Localize;
@ -38,7 +36,11 @@ impl SimplePluginCommand for Localize {
"FTL files to load",
Some('f'),
)
.switch("fallback", "Return message ID if translation not found", Some('F'))
.switch(
"fallback",
"Return message ID if translation not found",
Some('F'),
)
.category(Category::Strings)
}
@ -77,8 +79,12 @@ impl SimplePluginCommand for Localize {
let locale_code: String = call.req(1)?;
// Parse locale
let locale: LanguageIdentifier = locale_code.parse()
.map_err(|e| LabeledError::new("Invalid locale").with_label(format!("Invalid locale '{}': {}", locale_code, e), call.head))?;
let locale: LanguageIdentifier = locale_code.parse().map_err(|e| {
LabeledError::new("Invalid locale").with_label(
format!("Invalid locale '{}': {}", locale_code, e),
call.head,
)
})?;
// Create FluentBundle
let mut bundle = FluentBundle::new(vec![locale.clone()]);
@ -92,7 +98,8 @@ impl SimplePluginCommand for Localize {
let files = extract_file_list(files_value)?;
load_from_files(&mut bundle, &files)?;
} else {
return Err(LabeledError::new("Missing argument").with_label("Must provide either --bundle or --files", call.head));
return Err(LabeledError::new("Missing argument")
.with_label("Must provide either --bundle or --files", call.head));
}
// Prepare arguments for interpolation
@ -110,32 +117,37 @@ impl SimplePluginCommand for Localize {
// Return message ID as fallback
return Ok(Value::string(format!("[[{}]]", message_id), call.head));
} else {
return Err(LabeledError::new("Message not found").with_label(format!("Message '{}' not found in locale '{}'", message_id, locale_code), call.head));
return Err(LabeledError::new("Message not found").with_label(
format!(
"Message '{}' not found in locale '{}'",
message_id, locale_code
),
call.head,
));
}
}
};
// Format the message
let pattern = msg.value()
.ok_or_else(|| LabeledError::new("Message has no value").with_label(format!("Message '{}' has no value", message_id), call.head))?;
let pattern = msg.value().ok_or_else(|| {
LabeledError::new("Message has no value")
.with_label(format!("Message '{}' has no value", message_id), call.head)
})?;
let mut errors = vec![];
let formatted = bundle.format_pattern(
pattern,
fluent_args.as_ref(),
&mut errors
);
let formatted = bundle.format_pattern(pattern, fluent_args.as_ref(), &mut errors);
// Handle formatting errors
if !errors.is_empty() {
let error_msgs: Vec<String> = errors.iter()
.map(|e| format!("{:?}", e))
.collect();
return Err(LabeledError::new("Formatting error").with_label(format!(
"Formatting errors for message '{}': {}",
message_id,
error_msgs.join(", ")
), call.head));
let error_msgs: Vec<String> = errors.iter().map(|e| format!("{:?}", e)).collect();
return Err(LabeledError::new("Formatting error").with_label(
format!(
"Formatting errors for message '{}': {}",
message_id,
error_msgs.join(", ")
),
call.head,
));
}
Ok(Value::string(formatted.to_string(), call.head))
@ -153,7 +165,10 @@ fn load_from_bundle_value(
load_messages_from_value(bundle, messages_value)?;
}
}
_ => return Err(LabeledError::new("Invalid bundle").with_label("Bundle must be a record", nu_protocol::Span::unknown())),
_ => {
return Err(LabeledError::new("Invalid bundle")
.with_label("Bundle must be a record", nu_protocol::Span::unknown()))
}
}
Ok(())
}
@ -172,16 +187,27 @@ fn load_messages_from_value(
// Create a minimal FTL resource from the message
let ftl_content = format!("{} = {}", id, text);
let resource = FluentResource::try_new(ftl_content)
.map_err(|_| LabeledError::new("Invalid FTL").with_label(format!("Invalid FTL for message '{}'", id), nu_protocol::Span::unknown()))?;
let resource = FluentResource::try_new(ftl_content).map_err(|_| {
LabeledError::new("Invalid FTL").with_label(
format!("Invalid FTL for message '{}'", id),
nu_protocol::Span::unknown(),
)
})?;
bundle.add_resource(resource)
.map_err(|_| LabeledError::new("Failed to add message").with_label(format!("Failed to add message '{}'", id), nu_protocol::Span::unknown()))?;
bundle.add_resource(resource).map_err(|_| {
LabeledError::new("Failed to add message").with_label(
format!("Failed to add message '{}'", id),
nu_protocol::Span::unknown(),
)
})?;
}
}
}
}
_ => return Err(LabeledError::new("Invalid messages").with_label("Messages must be a list", nu_protocol::Span::unknown())),
_ => {
return Err(LabeledError::new("Invalid messages")
.with_label("Messages must be a list", nu_protocol::Span::unknown()))
}
}
Ok(())
}
@ -191,26 +217,40 @@ fn load_from_files(
files: &[String],
) -> Result<(), LabeledError> {
for file_path in files {
let content = std::fs::read_to_string(file_path)
.map_err(|e| LabeledError::new("Read error").with_label(format!("Failed to read '{}': {}", file_path, e), nu_protocol::Span::unknown()))?;
let content = std::fs::read_to_string(file_path).map_err(|e| {
LabeledError::new("Read error").with_label(
format!("Failed to read '{}': {}", file_path, e),
nu_protocol::Span::unknown(),
)
})?;
let resource = FluentResource::try_new(content)
.map_err(|e| LabeledError::new("Invalid FTL").with_label(format!("Invalid FTL in '{}': {:?}", file_path, e), nu_protocol::Span::unknown()))?;
let resource = FluentResource::try_new(content).map_err(|e| {
LabeledError::new("Invalid FTL").with_label(
format!("Invalid FTL in '{}': {:?}", file_path, e),
nu_protocol::Span::unknown(),
)
})?;
bundle.add_resource(resource)
.map_err(|e| LabeledError::new("Load error").with_label(format!("Failed to load '{}': {:?}", file_path, e), nu_protocol::Span::unknown()))?;
bundle.add_resource(resource).map_err(|e| {
LabeledError::new("Load error").with_label(
format!("Failed to load '{}': {:?}", file_path, e),
nu_protocol::Span::unknown(),
)
})?;
}
Ok(())
}
fn extract_file_list(files_value: Value) -> Result<Vec<String>, LabeledError> {
match files_value {
Value::List { vals, .. } => {
vals.iter()
.map(|v| value_to_string(v))
.collect::<Result<Vec<_>, _>>()
}
_ => Err(LabeledError::new("Invalid files").with_label("Files must be a list of strings", nu_protocol::Span::unknown())),
Value::List { vals, .. } => vals
.iter()
.map(|v| value_to_string(v))
.collect::<Result<Vec<_>, _>>(),
_ => Err(LabeledError::new("Invalid files").with_label(
"Files must be a list of strings",
nu_protocol::Span::unknown(),
)),
}
}
@ -224,16 +264,24 @@ fn convert_to_fluent_args(args_value: Value) -> Result<FluentArgs<'static>, Labe
Value::String { val, .. } => FluentValue::from(val.clone()),
Value::Int { val, .. } => FluentValue::from(*val as f64),
Value::Float { val, .. } => FluentValue::from(*val),
_ => return Err(LabeledError::new("Unsupported argument type").with_label(format!(
"Unsupported argument type for '{}': {:?}",
key,
value.get_type()
), nu_protocol::Span::unknown())),
_ => {
return Err(LabeledError::new("Unsupported argument type").with_label(
format!(
"Unsupported argument type for '{}': {:?}",
key,
value.get_type()
),
nu_protocol::Span::unknown(),
))
}
};
fluent_args.set(key.clone(), fluent_value);
}
}
_ => return Err(LabeledError::new("Invalid arguments").with_label("Arguments must be a record", nu_protocol::Span::unknown())),
_ => {
return Err(LabeledError::new("Invalid arguments")
.with_label("Arguments must be a record", nu_protocol::Span::unknown()))
}
}
Ok(fluent_args)
@ -247,7 +295,7 @@ fn value_to_string(value: &Value) -> Result<String, LabeledError> {
Value::Bool { val, .. } => Ok(val.to_string()),
_ => Err(LabeledError::new("Type conversion error").with_label(
format!("Cannot convert {:?} to string", value.get_type()),
nu_protocol::Span::unknown()
nu_protocol::Span::unknown(),
)),
}
}

View File

@ -1,14 +1,13 @@
mod parse_ftl;
mod localize;
mod validate_ftl;
mod create_bundle;
mod extract_messages;
mod list_locales;
mod create_bundle;
mod localize;
mod parse_ftl;
mod validate_ftl;
pub use parse_ftl::ParseFtl;
pub use localize::Localize;
pub use validate_ftl::ValidateFtl;
pub use create_bundle::CreateBundle;
pub use extract_messages::ExtractMessages;
pub use list_locales::ListLocales;
pub use create_bundle::CreateBundle;
pub use localize::Localize;
pub use parse_ftl::ParseFtl;
pub use validate_ftl::ValidateFtl;

View File

@ -1,11 +1,9 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{
Category, LabeledError, Signature, Span, SyntaxShape, Type, Value, record
};
use fluent_syntax::parser::parse;
use fluent_syntax::ast::{Entry, Message, Pattern, PatternElement};
use std::fs;
use crate::FluentPlugin;
use fluent_syntax::ast::{Entry, Message, Pattern, PatternElement};
use fluent_syntax::parser::parse;
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{record, Category, LabeledError, Signature, Span, SyntaxShape, Type, Value};
use std::fs;
pub struct ParseFtl;
@ -28,13 +26,11 @@ impl SimplePluginCommand for ParseFtl {
}
fn examples(&self) -> Vec<nu_protocol::Example<'_>> {
vec![
nu_protocol::Example {
description: "Parse an FTL file",
example: "fluent-parse locales/en-US/main.ftl",
result: None,
},
]
vec![nu_protocol::Example {
description: "Parse an FTL file",
example: "fluent-parse locales/en-US/main.ftl",
result: None,
}]
}
fn run(
@ -47,14 +43,21 @@ impl SimplePluginCommand for ParseFtl {
let file_path: String = call.req(0)?;
// Read FTL file
let ftl_content = fs::read_to_string(&file_path)
.map_err(|e| LabeledError::new("Read error").with_label(format!("Failed to read file '{}': {}", file_path, e), call.head))?;
let ftl_content = fs::read_to_string(&file_path).map_err(|e| {
LabeledError::new("Read error").with_label(
format!("Failed to read file '{}': {}", file_path, e),
call.head,
)
})?;
// Parse FTL content
let resource = match parse(ftl_content) {
Ok(res) => res,
Err((_, errors)) => {
return Err(LabeledError::new("Parse error").with_label(format!("Failed to parse FTL content: {:?}", errors), call.head));
return Err(LabeledError::new("Parse error").with_label(
format!("Failed to parse FTL content: {:?}", errors),
call.head,
));
}
};
@ -76,7 +79,10 @@ impl SimplePluginCommand for ParseFtl {
}
}
fn extract_messages_from_resource(resource: &fluent_syntax::ast::Resource<String>, span: Span) -> Result<Vec<Value>, String> {
fn extract_messages_from_resource(
resource: &fluent_syntax::ast::Resource<String>,
span: Span,
) -> Result<Vec<Value>, String> {
let mut messages = Vec::new();
for entry in &resource.body {
@ -113,8 +119,16 @@ fn extract_message_info(message: &Message<String>, span: Span) -> Result<Value,
}
// Extract comment if present
let comment = message.comment.as_ref()
.map(|c| c.content.iter().map(|line| line.trim()).collect::<Vec<_>>().join("\n"))
let comment = message
.comment
.as_ref()
.map(|c| {
c.content
.iter()
.map(|line| line.trim())
.collect::<Vec<_>>()
.join("\n")
})
.unwrap_or_default();
Ok(Value::record(
@ -129,7 +143,8 @@ fn extract_message_info(message: &Message<String>, span: Span) -> Result<Value,
}
fn extract_pattern_text(pattern: &Pattern<String>) -> String {
pattern.elements
pattern
.elements
.iter()
.map(|element| match element {
PatternElement::TextElement { value } => value.to_string(),

View File

@ -1,7 +1,7 @@
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{Category, LabeledError, Signature, SyntaxShape, Type, Value, record};
use fluent_syntax::parser::parse;
use crate::FluentPlugin;
use fluent_syntax::parser::parse;
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand, SimplePluginCommand};
use nu_protocol::{record, Category, LabeledError, Signature, SyntaxShape, Type, Value};
pub struct ValidateFtl;
@ -24,13 +24,11 @@ impl SimplePluginCommand for ValidateFtl {
}
fn examples(&self) -> Vec<nu_protocol::Example<'_>> {
vec![
nu_protocol::Example {
description: "Validate an FTL file",
example: "fluent-validate locales/en-US/main.ftl",
result: None,
},
]
vec![nu_protocol::Example {
description: "Validate an FTL file",
example: "fluent-validate locales/en-US/main.ftl",
result: None,
}]
}
fn run(
@ -42,8 +40,10 @@ impl SimplePluginCommand for ValidateFtl {
) -> Result<Value, LabeledError> {
let file_path: String = call.req(0)?;
let content = std::fs::read_to_string(&file_path)
.map_err(|e| LabeledError::new("Read error").with_label(format!("Failed to read '{}': {}", file_path, e), call.head))?;
let content = std::fs::read_to_string(&file_path).map_err(|e| {
LabeledError::new("Read error")
.with_label(format!("Failed to read '{}': {}", file_path, e), call.head)
})?;
let parse_result = parse(content);
let result = match parse_result {
@ -56,7 +56,8 @@ impl SimplePluginCommand for ValidateFtl {
call.head,
),
Err((_resource, errors)) => {
let error_list: Vec<Value> = errors.iter()
let error_list: Vec<Value> = errors
.iter()
.map(|err| Value::string(format!("{:?}", err), call.head))
.collect();

View File

@ -22,4 +22,3 @@ impl Plugin for FluentPlugin {
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ keywords = [
categories = ["algorithms"]
repository = "https://github.com/ArmoredPony/nu_plugin_hashes"
license = "MIT"
version = "0.109.1"
version = "0.111.0"
edition = "2024"
[features]
@ -37,9 +37,9 @@ default = [
]
[dependencies]
nu-cmd-base = "0.109.1"
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
nu-cmd-base = "0.111.0"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
digest = "0.10.7"
[dependencies.ascon-hash]
@ -215,9 +215,8 @@ optional = true
version = "0.10.4"
optional = true
[dev-dependencies.nu-plugin-test-support]
version = "0.109.1"
path = "../nushell/crates/nu-plugin-test-support"
[dev-dependencies]
nu-plugin-test-support = "0.111.0"
[profile.release]
strip = true

View File

@ -1,225 +0,0 @@
[package]
name = "nu_plugin_hashes"
description = "A Nushell plugin that adds 63 cryptographic hash functions from Hashes project"
keywords = [
"nu",
"plugin",
"hash",
]
categories = ["algorithms"]
repository = "https://github.com/ArmoredPony/nu_plugin_hashes"
license = "MIT"
version = "0.109.0"
edition = "2024"
[features]
default = [
"ascon-hash",
"belt-hash",
"blake2",
"blake3",
"fsb",
"gost94",
"groestl",
"jh",
"md2",
"md4",
"ripemd",
"sha1",
"sha2",
"sha3",
"shabal",
"skein",
"sm3",
"streebog",
"tiger",
"whirlpool",
]
[dependencies]
nu-cmd-base = "0.109.1"
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
digest = "0.10.7"
[dependencies.ascon-hash]
version = "0.3.1"
optional = true
[dependencies.belt-hash]
version = "0.1.1"
optional = true
[dependencies.blake2]
version = "0.10.6"
optional = true
[dependencies.blake3]
version = "1.8.2"
optional = true
default-features = false
features = [
"std",
"traits-preview",
]
[dependencies.fsb]
version = "0.1.3"
optional = true
[dependencies.gost94]
version = "0.10.4"
optional = true
[dependencies.groestl]
version = "0.10.1"
optional = true
[dependencies.jh]
version = "0.1.0"
optional = true
[dependencies.md2]
version = "0.10.2"
optional = true
[dependencies.md4]
version = "0.10.2"
optional = true
[dependencies.ripemd]
version = "0.1.3"
optional = true
[dependencies.sha1]
version = "0.10.6"
optional = true
[dependencies.sha2]
version = "0.10.9"
optional = true
[dependencies.sha3]
version = "0.10.8"
optional = true
[dependencies.shabal]
version = "0.4.1"
optional = true
[dependencies.skein]
version = "0.1.1"
optional = true
[dependencies.sm3]
version = "0.4.2"
optional = true
[dependencies.streebog]
version = "0.10.2"
optional = true
[dependencies.tiger]
version = "0.2.1"
optional = true
[dependencies.whirlpool]
version = "0.10.4"
optional = true
[build-dependencies]
digest = "0.10.7"
[build-dependencies.ascon-hash]
version = "0.3.1"
optional = true
[build-dependencies.belt-hash]
version = "0.1.1"
optional = true
[build-dependencies.blake2]
version = "0.10.6"
optional = true
[build-dependencies.blake3]
version = "1.8.2"
optional = true
default-features = false
features = [
"std",
"traits-preview",
]
[build-dependencies.fsb]
version = "0.1.3"
optional = true
[build-dependencies.gost94]
version = "0.10.4"
optional = true
[build-dependencies.groestl]
version = "0.10.1"
optional = true
[build-dependencies.jh]
version = "0.1.0"
optional = true
[build-dependencies.md2]
version = "0.10.2"
optional = true
[build-dependencies.md4]
version = "0.10.2"
optional = true
[build-dependencies.ripemd]
version = "0.1.3"
optional = true
[build-dependencies.sha1]
version = "0.10.6"
optional = true
[build-dependencies.sha2]
version = "0.10.9"
optional = true
[build-dependencies.sha3]
version = "0.10.8"
optional = true
[build-dependencies.shabal]
version = "0.4.1"
optional = true
[build-dependencies.skein]
version = "0.1.1"
optional = true
[build-dependencies.sm3]
version = "0.4.2"
optional = true
[build-dependencies.streebog]
version = "0.10.2"
optional = true
[build-dependencies.tiger]
version = "0.2.1"
optional = true
[build-dependencies.whirlpool]
version = "0.10.4"
optional = true
[dev-dependencies.nu-plugin-test-support]
version = "0.109.0"
path = "../nushell/crates/nu-plugin-test-support"
[profile.release]
strip = true
lto = true
codegen-units = 1

View File

@ -13,20 +13,25 @@ crate.
## Installation
To install this plugin with all algorithms available run
```nu
```nushell
nu
cargo install nu_plugin_hashes
plugin add ($env.CARGO_HOME ++ /bin/nu_plugin_hashes)
```
or on Windows
```nu
```nushell
nu
cargo install nu_plugin_hashes
plugin add ($env.CARGO_HOME ++ /bin/nu_plugin_hashes.exe)
```
After loading the plugin, execute `help hash` to list newly added commands
```nu
```nushell
nu
~> help hash
Apply hash function.
@ -47,12 +52,16 @@ Subcommands:
If you only need some algorithms, disable default features and select only
those you need
```nu
```nushell
nu
cargo install nu_plugin_hashes --no-default-features --features sha2,streebog
```
Then check what's installed
```nu
```nushell
nu
~> help hash
Apply hash function.

File diff suppressed because it is too large Load Diff

View File

@ -7,167 +7,154 @@
use std::{io::Write, marker::PhantomData, ops::Not};
use digest::{Digest, Output};
use nu_cmd_base::input_handler::{operate, CmdArgument};
use nu_cmd_base::input_handler::{CmdArgument, operate};
use nu_plugin::{EngineInterface, EvaluatedCall, PluginCommand};
use nu_protocol::{
ast::CellPath,
Category,
Example,
IntoPipelineData,
LabeledError,
PipelineData,
ShellError,
Signature,
Span,
SyntaxShape,
Type,
Value,
Category, Example, IntoPipelineData, LabeledError, PipelineData, ShellError, Signature, Span,
SyntaxShape, Type, Value, ast::CellPath,
};
use crate::HashesPlugin;
pub trait Hasher: Digest + Clone {
fn name() -> &'static str;
fn examples() -> Vec<Example<'static>>;
fn name() -> &'static str;
fn examples() -> Vec<Example<'static>>;
}
#[derive(Clone)]
pub struct GenericHasher<H: Hasher> {
name: String,
description: String,
_hasher: PhantomData<H>,
name: String,
description: String,
_hasher: PhantomData<H>,
}
impl<H: Hasher> Default for GenericHasher<H> {
fn default() -> Self {
Self {
name: format!("hash {}", H::name()),
description: format!(
"Hash a value using the {} hash algorithm.",
H::name()
),
_hasher: PhantomData,
fn default() -> Self {
Self {
name: format!("hash {}", H::name()),
description: format!("Hash a value using the {} hash algorithm.", H::name()),
_hasher: PhantomData,
}
}
}
}
struct Arguments {
cell_paths: Option<Vec<CellPath>>,
binary: bool,
cell_paths: Option<Vec<CellPath>>,
binary: bool,
}
impl CmdArgument for Arguments {
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
fn take_cell_paths(&mut self) -> Option<Vec<CellPath>> {
self.cell_paths.take()
}
}
impl<H> PluginCommand for GenericHasher<H>
where
H: Hasher + Write + Send + Sync + 'static,
Output<H>: core::fmt::LowerHex,
H: Hasher + Write + Send + Sync + 'static,
Output<H>: core::fmt::LowerHex,
{
type Plugin = HashesPlugin;
type Plugin = HashesPlugin;
fn name(&self) -> &str {
&self.name
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Hash)
.input_output_types(vec![
(Type::Binary, Type::Any),
(Type::String, Type::Any),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.switch(
"binary",
"Output binary instead of hexadecimal representation",
Some('b'),
)
.rest(
"rest",
SyntaxShape::CellPath,
format!("Optionally {} hash data by cell path.", H::name()),
)
}
fn description(&self) -> &str {
&self.description
}
fn examples(&self) -> Vec<Example<'_>> {
H::examples()
}
fn run(
&self,
_plugin: &HashesPlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
) -> Result<PipelineData, LabeledError> {
let head = call.head;
let binary = call.has_flag("binary")?;
let cell_paths: Vec<CellPath> = call.rest(0)?;
let cell_paths = cell_paths.is_empty().not().then_some(cell_paths);
if let PipelineData::ByteStream(stream, ..) = input {
let mut hasher = H::new();
stream.write_to(&mut hasher)?;
let digest = hasher.finalize();
if binary {
Ok(Value::binary(digest.to_vec(), head).into_pipeline_data())
} else {
Ok(Value::string(format!("{digest:x}"), head).into_pipeline_data())
}
} else {
operate(
action::<H>,
Arguments { binary, cell_paths },
input,
head,
engine.signals(),
)
.map_err(Into::into)
fn name(&self) -> &str {
&self.name
}
fn signature(&self) -> Signature {
Signature::build(self.name())
.category(Category::Hash)
.input_output_types(vec![
(Type::Binary, Type::Any),
(Type::String, Type::Any),
(Type::table(), Type::table()),
(Type::record(), Type::record()),
])
.allow_variants_without_examples(true)
.switch(
"binary",
"Output binary instead of hexadecimal representation",
Some('b'),
)
.rest(
"rest",
SyntaxShape::CellPath,
format!("Optionally {} hash data by cell path.", H::name()),
)
}
fn description(&self) -> &str {
&self.description
}
fn examples(&self) -> Vec<Example<'_>> {
H::examples()
}
fn run(
&self,
_plugin: &HashesPlugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData,
) -> Result<PipelineData, LabeledError> {
let head = call.head;
let binary = call.has_flag("binary")?;
let cell_paths: Vec<CellPath> = call.rest(0)?;
let cell_paths = cell_paths.is_empty().not().then_some(cell_paths);
if let PipelineData::ByteStream(stream, ..) = input {
let mut hasher = H::new();
stream.write_to(&mut hasher)?;
let digest = hasher.finalize();
if binary {
Ok(Value::binary(digest.to_vec(), head).into_pipeline_data())
} else {
Ok(Value::string(format!("{digest:x}"), head).into_pipeline_data())
}
} else {
operate(
action::<H>,
Arguments { binary, cell_paths },
input,
head,
engine.signals(),
)
.map_err(Into::into)
}
}
}
}
fn action<H>(input: &Value, args: &Arguments, _span: Span) -> Value
where
H: Hasher,
Output<H>: core::fmt::LowerHex,
H: Hasher,
Output<H>: core::fmt::LowerHex,
{
let span = input.span();
let (bytes, span) = match input {
Value::String { val, .. } => (val.as_bytes(), span),
Value::Binary { val, .. } => (val.as_slice(), span),
// Propagate existing errors
Value::Error { .. } => return input.clone(),
other => {
let span = input.span();
let span = input.span();
let (bytes, span) = match input {
Value::String { val, .. } => (val.as_bytes(), span),
Value::Binary { val, .. } => (val.as_slice(), span),
// Propagate existing errors
Value::Error { .. } => return input.clone(),
other => {
let span = input.span();
return Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string or binary".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
},
span,
);
return Value::error(
ShellError::OnlySupportsThisInputType {
exp_input_type: "string or binary".into(),
wrong_type: other.get_type().to_string(),
dst_span: span,
src_span: other.span(),
},
span,
);
}
};
let digest = H::digest(bytes);
if args.binary {
Value::binary(digest.to_vec(), span)
} else {
Value::string(format!("{digest:x}"), span)
}
};
let digest = H::digest(bytes);
if args.binary {
Value::binary(digest.to_vec(), span)
} else {
Value::string(format!("{digest:x}"), span)
}
}

View File

@ -7,11 +7,11 @@ use nu_plugin::Plugin;
pub struct HashesPlugin;
impl Plugin for HashesPlugin {
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
fn version(&self) -> String {
env!("CARGO_PKG_VERSION").into()
}
fn commands(&self) -> Vec<Box<dyn nu_plugin::PluginCommand<Plugin = Self>>> {
commands_generated::commands()
}
fn commands(&self) -> Vec<Box<dyn nu_plugin::PluginCommand<Plugin = Self>>> {
commands_generated::commands()
}
}

View File

@ -1,6 +1,6 @@
use nu_plugin::{serve_plugin, MsgPackSerializer};
use nu_plugin::{MsgPackSerializer, serve_plugin};
use nu_plugin_hashes::HashesPlugin;
fn main() {
serve_plugin(&HashesPlugin, MsgPackSerializer);
serve_plugin(&HashesPlugin, MsgPackSerializer);
}

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "nu_plugin_highlight"
version = "1.4.7+0.105.2"
version = "0.111.0"
authors = ["Tim 'Piepmatz' Hesse"]
edition = "2024"
repository = "https://github.com/cptpiepmatz/nu-plugin-highlight"
@ -26,9 +26,9 @@ version = "0.26"
default-features = false
[dependencies]
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
nu-path = "0.109.1"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
nu-path = "0.111.0"
nu-ansi-term = "0.50"
ansi_colours = "1"
mime_guess = "2"

View File

@ -16,8 +16,8 @@
</div>
## About
`nu-plugin-highlight` is a plugin for [Nushell](https://www.nushell.sh) that
provides syntax highlighting for source code.
It uses the [`syntect`](https://crates.io/crates/syntect) library for syntax
@ -26,9 +26,12 @@ access to its ready-to-use assets.
Custom themes can be loaded too.
## Usage
The `highlight` command can be used for syntax highlighting source code.
Here are a few examples:
```nushell
nushell
# Highlight a Markdown file by guessing the type from the pipeline metadata
open README.md | highlight
@ -49,11 +52,13 @@ highlight --list-themes
```
### Parameters
- `language <string>`:
This is an optional parameter that can be used to specify the language or file
extension to aid language detection.
### Flags
- `-h, --help`:
Display the help message for the highlight command.
@ -64,18 +69,23 @@ highlight --list-themes
List all possible themes.
## Configuration
The plugin can be configured using the
[`$env.config.plugins.highlight`](https://github.com/nushell/nushell/pull/10955)
variable.
### `true_colors`
Enable or disable true colors (24-bit).
By default, this is enabled.
```nushell
nushell
$env.config.plugins.highlight.true_colors = true
```
### `theme`
Set a theme to use.
The default theme depends on the operating system.
Use `highlight --list-themes | where default == true` to see your default theme.
@ -83,24 +93,31 @@ Setting this environment variable should allow
`highlight --list-themes | where id == $env.config.plugins.highlight.theme` to
result in a single row with your selected theme.
If you get no results, you have set an invalid theme.
```nushell
nushell
$env.config.plugins.highlight.theme = ansi
```
### `custom_themes`
Set a directory to load custom themes from.
Using `synctect`s theme loader, you can load custom themes in the `.tmtheme`
format from a directory that is passed as this configuration value.
```nushell
```toml
nushell
$env.config.plugins.highlight.custom_themes = ~/.nu/highlight/themes
```
## Plugin Installation
Installing and registering the `nu-plugin-highlight` is a straightforward
process.
Follow these steps:
1. Install the plugin from crates.io using cargo:
```nushell
cargo install nu_plugin_highlight
```
@ -108,6 +125,7 @@ Follow these steps:
2. Restart your terminal session to ensure the newly installed plugin is recognized.
3. Find path of your installation:
```nushell
which nu_plugin_highlight
```
@ -115,6 +133,7 @@ Follow these steps:
4. Register the plugin with Nushell:
If you are using a version **lower** than **0.93.0**, use `register` instead of `plugin add`.
```nushell
plugin add path/to/the/plugin/binary
```
@ -124,6 +143,7 @@ Follow these steps:
Tip: You can simply restart the shell or terminal. When nushell starts, it loads all plugins.
If you are using a version **lower** than **0.93.0**, you do **not need** to do this.
```nushell
plugin use highlight
```
@ -131,10 +151,12 @@ Follow these steps:
After registering, the plugin is available as part of your set of commands:
```nushell
nushell
help commands | where command_type == "plugin"
```
## Version Numbering
Starting with version `v1.1.0`, the version number of `nu-plugin-highlight`
incorporates the version number of its dependency, `nu-plugin`.
This is denoted in the format `v1.1.0+0.90.1`, where `v1.1.0` refers to the
@ -142,5 +164,6 @@ version of `nu-plugin-highlight` and `0.90.1` refers to the version of the
`nu-plugin` dependency.
## License
`nu_plugin_highlight` is licensed under the MIT License.
See [LICENSE](LICENSE) for more information.

View File

@ -2,11 +2,11 @@ use std::ops::Deref;
use std::path::Path;
use bat::assets::HighlightingAssets;
use bat::theme::{default_theme, ColorScheme};
use bat::theme::{ColorScheme, default_theme};
use syntect::LoadingError;
use syntect::easy::HighlightLines;
use syntect::highlighting::ThemeSet;
use syntect::parsing::{SyntaxReference, SyntaxSet};
use syntect::LoadingError;
use crate::terminal;
use crate::theme::{ListThemes, ThemeDescription};
@ -17,7 +17,7 @@ const SYNTAX_SET: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/syntax_set.b
pub struct Highlighter {
syntax_set: SyntaxSet,
highlighting_assets: HighlightingAssets,
custom_themes: Option<ThemeSet>
custom_themes: Option<ThemeSet>,
}
impl Highlighter {
@ -27,13 +27,13 @@ impl Highlighter {
syntax_set: syntect::dumps::from_uncompressed_data(SYNTAX_SET)
.expect("Failed to load syntax set"),
highlighting_assets: HighlightingAssets::from_binary(),
custom_themes: None
custom_themes: None,
}
}
pub fn custom_themes_from_folder(
&mut self,
path: impl AsRef<Path>
path: impl AsRef<Path>,
) -> Result<(), LoadingError> {
let path = nu_path::expand_to_real_path(path);
self.custom_themes = Some(ThemeSet::load_from_folder(path)?);
@ -53,7 +53,7 @@ impl Highlighter {
id: t_id.to_owned(),
name: theme.name.clone(),
author: theme.author.clone(),
default: default_theme_id == t_id
default: default_theme_id == t_id,
}
})
.collect();
@ -64,7 +64,7 @@ impl Highlighter {
id: id.to_owned(),
name: theme.name.clone(),
author: theme.author.clone(),
default: default_theme_id == id
default: default_theme_id == id,
});
}
}
@ -91,7 +91,7 @@ impl Highlighter {
input: &str,
language: Option<&str>,
theme: Option<&str>,
true_colors: bool
true_colors: bool,
) -> String {
let syntax_set = &self.syntax_set;
let syntax_ref: Option<&SyntaxReference> = match language {
@ -118,7 +118,7 @@ impl Highlighter {
.or_else(|| syntax_set.find_syntax_by_extension(&language_lowercase))
.or_else(|| syntax_set.find_syntax_by_extension(&language_capitalized))
}
_ => None
_ => None,
};
let syntax_ref = syntax_ref
.or(syntax_set.find_syntax_by_first_line(input))
@ -126,7 +126,7 @@ impl Highlighter {
let theme_id = match theme {
None => default_theme(ColorScheme::Dark),
Some(theme) => theme
Some(theme) => theme,
};
let theme = self
.custom_themes
@ -143,10 +143,11 @@ impl Highlighter {
// insert a newline in between lines, this is necessary for bats syntax set
let l = match i == line_count - 1 {
false => format!("{}\n", l.trim_end()),
true => l.trim_end().to_owned()
true => l.trim_end().to_owned(),
};
let styled_lines = highlighter.highlight_line(&l, syntax_set)
let styled_lines = highlighter
.highlight_line(&l, syntax_set)
.expect("Failed to highlight line");
styled_lines
.iter()

View File

@ -1,4 +1,4 @@
use nu_plugin::{serve_plugin, MsgPackSerializer};
use nu_plugin::{MsgPackSerializer, serve_plugin};
use plugin::HighlightPlugin;
mod highlight;

View File

@ -6,7 +6,7 @@ use nu_plugin::{EngineInterface, EvaluatedCall, Plugin, PluginCommand};
use nu_protocol::shell_error::io::IoError;
use nu_protocol::{
Category, DataSource, ErrorLabel, Example, FromValue, IntoValue, LabeledError, PipelineData,
PipelineMetadata, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value
PipelineMetadata, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value,
};
use syntect::LoadingError;
@ -29,7 +29,7 @@ impl Plugin for HighlightPlugin {
struct Config {
pub theme: Option<Spanned<String>>,
pub true_colors: Option<bool>,
pub custom_themes: Option<Spanned<PathBuf>>
pub custom_themes: Option<Spanned<PathBuf>>,
}
struct Highlight;
@ -46,13 +46,13 @@ impl PluginCommand for Highlight {
.optional(
"language",
SyntaxShape::String,
"language or file extension to help language detection"
"language or file extension to help language detection",
)
.named(
"theme",
SyntaxShape::String,
"them used for highlighting",
Some('t')
Some('t'),
)
.switch("list-themes", "list all possible themes", None)
.category(Category::Strings)
@ -66,8 +66,8 @@ impl PluginCommand for Highlight {
(String::from("author"), Type::String),
(String::from("default"), Type::Bool),
]
.into()
)
.into(),
),
)
}
@ -80,7 +80,7 @@ impl PluginCommand for Highlight {
_plugin: &Self::Plugin,
engine: &EngineInterface,
call: &EvaluatedCall,
input: PipelineData
input: PipelineData,
) -> Result<PipelineData, LabeledError> {
let mut highlighter = Highlighter::new();
@ -96,17 +96,17 @@ impl PluginCommand for Highlight {
err,
custom_themes_path.span,
custom_themes_path.item,
"Error while loading custom themes"
)
)))
"Error while loading custom themes",
),
)));
}
Err(err) => {
return Err(labeled_error(
err,
"Error while loading custom themes",
custom_themes_path.span,
None
))
None,
));
}
}
}
@ -117,14 +117,15 @@ impl PluginCommand for Highlight {
.transpose()?
.or(config.theme);
if let Some(theme) = &theme
&& !highlighter.is_valid_theme(&theme.item) {
return Err(labeled_error(
"use `highlight --list-themes` to list all themes",
format!("Unknown passed theme {:?}", &theme.item),
theme.span,
None
));
}
&& !highlighter.is_valid_theme(&theme.item)
{
return Err(labeled_error(
"use `highlight --list-themes` to list all themes",
format!("Unknown passed theme {:?}", &theme.item),
theme.span,
None,
));
}
let theme = theme.map(|spanned| spanned.item);
let theme = theme.as_deref();
@ -152,35 +153,35 @@ impl PluginCommand for Highlight {
fn examples(&self) -> Vec<Example<'_>> {
const fn example<'e>(description: &'e str, example: &'e str) -> Example<'e>
where
'e: 'static
'e: 'static,
{
Example {
example,
description,
result: None
result: None,
}
}
vec![
example(
"Highlight a Markdown file by guessing the type from the pipeline metadata",
"open README.md | highlight"
"open README.md | highlight",
),
example(
"Highlight a toml file by its file extension",
"open Cargo.toml -r | echo $in | highlight toml"
"open Cargo.toml -r | echo $in | highlight toml",
),
example(
"Highlight a rust file by programming language",
"open src/main.rs | echo $in | highlight Rust"
"open src/main.rs | echo $in | highlight Rust",
),
example(
"Highlight a bash script by inferring the language (needs shebang)",
"open example.sh | echo $in | highlight"
"open example.sh | echo $in | highlight",
),
example(
"Highlight a toml file with another theme",
"open Cargo.toml -r | highlight -t ansi"
"open Cargo.toml -r | highlight -t ansi",
),
example("List all available themes", "highlight --list-themes"),
]
@ -189,7 +190,7 @@ impl PluginCommand for Highlight {
fn language_hint(
call: &EvaluatedCall,
metadata: Option<&PipelineMetadata>
metadata: Option<&PipelineMetadata>,
) -> Result<Option<String>, ShellError> {
// first use passed argument
let arg = call.opt(0)?.map(String::from_value).transpose()?;
@ -206,15 +207,14 @@ fn language_hint(
"x-toml" => Some("toml".to_string()),
"x-nuscript" | "x-nushell" | "x-nuon" => Some("nushell".to_string()),
s if s.starts_with("x-") => None, // we cannot be sure about this type,
_ => Some(sub_type)
_ => Some(sub_type),
}
};
// as last resort, try to use the extension of data source
let data_source = || -> Option<String> {
let data_source = &metadata?.data_source;
let DataSource::FilePath(path) = data_source
else {
let DataSource::FilePath(path) = data_source else {
return None;
};
let extension = path.extension()?.to_string_lossy();
@ -229,20 +229,20 @@ fn labeled_error(
msg: impl ToString,
label: impl ToString,
span: Span,
inner: impl Into<Option<ShellError>>
inner: impl Into<Option<ShellError>>,
) -> LabeledError {
LabeledError {
msg: msg.to_string(),
labels: Box::new(vec![ErrorLabel {
text: label.to_string(),
span
span,
}]),
code: None,
url: None,
help: None,
inner: match inner.into() {
Some(inner) => Box::new(vec![inner.into()]),
None => Box::new(vec![])
}
None => Box::new(vec![]),
},
}
}

View File

@ -6,7 +6,7 @@ pub struct ThemeDescription {
pub id: String,
pub name: Option<String>,
pub author: Option<String>,
pub default: bool
pub default: bool,
}
/// List of theme descriptions.

View File

@ -1,24 +1,31 @@
# Nushell syntax highlight for sublime text
- Just copied matlab syntax file and modified it for nushell
- Use nushell lsp language server (need lsp sublime package)
## Installation
1. In sublime text install the LSP package.
2. After cloning and cd-ing into the directory, in nushell run (tested in Ubuntu 20.04):
```nu
```nushell
nu
ls
| find -v README & export & color
| get name
| ansi strip
| each {|file|
cp -f $file (~/.config/sublime-text/Packages/User | path expand)
cp -f $file (~/.config/sublime-text/Packages/User | path expand)
}
```
3. In sublime, open `Preferences > Customize Color Scheme` and add the contents of `sublime-color-scheme`. Modify to your liking.
1. In sublime, open `Preferences > Customize Color Scheme` and add the contents of `sublime-color-scheme`. Modify to your liking.
## Update commands
If you need to update nushell functions or add your custom commands and aliases, run:
```nu
```nushell
nu
chmod +x export.nu
./export.nu
```

View File

@ -90,12 +90,6 @@ version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -232,7 +226,7 @@ dependencies = [
"num-traits",
"pastey",
"rayon",
"thiserror 2.0.12",
"thiserror 2.0.18",
"v_frame",
"y4m",
]
@ -273,7 +267,7 @@ dependencies = [
"proc-macro2",
"quote",
"regex",
"rustc-hash",
"rustc-hash 1.1.0",
"shlex",
"syn 2.0.104",
]
@ -432,18 +426,17 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]]
name = "chrono"
version = "0.4.41"
version = "0.4.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c469d952047f47f91b68d1cba3f10d63c11d73e4636f24f08daf0278abf01c4d"
checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"pure-rust-locales",
"serde",
"wasm-bindgen",
"windows-link 0.1.3",
"windows-link 0.2.1",
]
[[package]]
@ -486,6 +479,7 @@ dependencies = [
"anstyle",
"clap_lex",
"strsim",
"terminal_size",
]
[[package]]
@ -597,7 +591,7 @@ dependencies = [
"document-features",
"mio",
"parking_lot",
"rustix 1.0.7",
"rustix",
"signal-hook",
"signal-hook-mio",
"winapi",
@ -675,6 +669,17 @@ dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "displaydoc"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.104",
]
[[package]]
name = "doctest-file"
version = "1.0.0"
@ -768,9 +773,9 @@ dependencies = [
[[package]]
name = "fancy-regex"
version = "0.16.2"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "998b056554fbe42e03ae0e152895cd1a7e1002aec800fdc6635d20270260c46f"
checksum = "72cf461f865c862bb7dc573f643dd6a2b6842f7c30b07882b56bd148cc2761b8"
dependencies = [
"bit-set",
"regex-automata",
@ -817,10 +822,55 @@ dependencies = [
]
[[package]]
name = "foldhash"
version = "0.1.5"
name = "fluent"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
checksum = "8137a6d5a2c50d6b0ebfcb9aaa91a28154e0a70605f112d30cb0cd4a78670477"
dependencies = [
"fluent-bundle",
"unic-langid",
]
[[package]]
name = "fluent-bundle"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01203cb8918f5711e73891b347816d932046f95f54207710bda99beaeb423bf4"
dependencies = [
"fluent-langneg",
"fluent-syntax",
"intl-memoizer",
"intl_pluralrules",
"rustc-hash 2.1.1",
"self_cell",
"smallvec",
"unic-langid",
]
[[package]]
name = "fluent-langneg"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eebbe59450baee8282d71676f3bfed5689aeab00b27545e83e5f14b1195e8b0"
dependencies = [
"unic-langid",
]
[[package]]
name = "fluent-syntax"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "54f0d287c53ffd184d04d8677f590f4ac5379785529e5e08b1c8083acdd5c198"
dependencies = [
"memchr",
"thiserror 2.0.18",
]
[[package]]
name = "foldhash"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
[[package]]
name = "getrandom"
@ -885,9 +935,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.4"
version = "0.16.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5"
checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
dependencies = [
"allocator-api2",
"equivalent",
@ -1033,12 +1083,12 @@ dependencies = [
[[package]]
name = "indexmap"
version = "2.11.4"
version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b0f83760fb341a774ed326568e19f5a863af4a952def8c39f9ab92fd95b88e5"
checksum = "7714e70437a7dc3ac8eb7e6f8df75fd8eb422675fc7678aff7364301092b1017"
dependencies = [
"equivalent",
"hashbrown 0.15.4",
"hashbrown 0.16.1",
]
[[package]]
@ -1054,9 +1104,9 @@ dependencies = [
[[package]]
name = "interprocess"
version = "2.2.3"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d941b405bd2322993887859a8ee6ac9134945a24ec5ec763a8a962fc64dfec2d"
checksum = "6be5e5c847dbdb44564bd85294740d031f4f8aeb3464e5375ef7141f7538db69"
dependencies = [
"doctest-file",
"libc",
@ -1065,6 +1115,25 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "intl-memoizer"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "310da2e345f5eb861e7a07ee182262e94975051db9e4223e909ba90f392f163f"
dependencies = [
"type-map",
"unic-langid",
]
[[package]]
name = "intl_pluralrules"
version = "7.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "078ea7b7c29a2b4df841a7f6ac8775ff6074020c6776d48491ce2268e068f972"
dependencies = [
"unic-langid",
]
[[package]]
name = "inventory"
version = "0.3.20"
@ -1176,9 +1245,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.174"
version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
[[package]]
name = "libflate"
@ -1251,12 +1320,6 @@ dependencies = [
"libc",
]
[[package]]
name = "linux-raw-sys"
version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "linux-raw-sys"
version = "0.9.4"
@ -1296,11 +1359,11 @@ dependencies = [
[[package]]
name = "lru"
version = "0.12.5"
version = "0.16.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38"
checksum = "a1dc47f592c06f33f8e3aea9591776ec7c9f9e4124778ff8a3c3b87159f7e593"
dependencies = [
"hashbrown 0.15.4",
"hashbrown 0.16.1",
]
[[package]]
@ -1315,12 +1378,9 @@ dependencies = [
[[package]]
name = "mach2"
version = "0.4.3"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44"
dependencies = [
"libc",
]
checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
[[package]]
name = "matrixmultiply"
@ -1344,9 +1404,9 @@ dependencies = [
[[package]]
name = "memchr"
version = "2.7.5"
version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79"
[[package]]
name = "miette"
@ -1483,9 +1543,9 @@ dependencies = [
[[package]]
name = "nu-derive-value"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465d2d3ada6004cb6689f269a08c70ba81056231e2b5392d1e0ccf5825f81cb"
checksum = "d71958b54c367bda033f7dcc4a73b61972fb52323f71a1e3533e290fa67148d1"
dependencies = [
"heck",
"proc-macro-error2",
@ -1496,9 +1556,9 @@ dependencies = [
[[package]]
name = "nu-engine"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3b777faf7c5180fe5d7f67d83c44fd14138d91f2938a36494ed6ac66b7160f3"
checksum = "d41b3e3e2d25c30741a0761856258e22624c0d60064e4f0e12f86202a451d492"
dependencies = [
"fancy-regex",
"log",
@ -1511,25 +1571,25 @@ dependencies = [
[[package]]
name = "nu-experimental"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73dd212a1afdad646a38c00579a0988264880aeb97fee820b349a28cdcc04df2"
checksum = "f328fa0531bdf49c2dc0312b40cb780e3d74e0d3dbb15d508469a5ae4cfd8d8f"
dependencies = [
"itertools 0.14.0",
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
name = "nu-glob"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15aa2c17078926f14e393b4b708e69f228cb6fd4c81136839bde82772bdde1b5"
checksum = "01ee787f61353c9c90581ddf4c0602a07b991cdd06c97dac8b6d323a1a52c43a"
[[package]]
name = "nu-path"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dde9d8ba26f62c07176c0237a36f38ce964ab3a0dcfb6aab1feea7515d1c6594"
checksum = "c01d110cb931acf56237ce572e5b156e8e1134227c90deeffb92eedda9482c23"
dependencies = [
"dirs",
"omnipath",
@ -1539,9 +1599,9 @@ dependencies = [
[[package]]
name = "nu-plugin"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea1fbfd41b2f5c967675fc948831e03be67d91c6b8e18a60f3445113fe6548c"
checksum = "c322531b1a7d6338c5ead1f454294f46babf8c99cd4716311cab1e88ba52b154"
dependencies = [
"log",
"nix",
@ -1550,14 +1610,14 @@ dependencies = [
"nu-plugin-protocol",
"nu-protocol",
"nu-utils",
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
name = "nu-plugin-core"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd2410648c2c38cf9359595ffcf281d9d60a81c0580ff07f7c7d42bed414f3a1"
checksum = "38ee792aeb0d37e0ed55ca4304e434eece497914e27ae42616a8bb973f5d2720"
dependencies = [
"interprocess",
"log",
@ -1566,14 +1626,14 @@ dependencies = [
"rmp-serde",
"serde",
"serde_json",
"windows 0.62.2",
"windows",
]
[[package]]
name = "nu-plugin-protocol"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "27de26da922261dff8103a811879228c55749a1b7b0e573b639c609a0651a01e"
checksum = "7725f341428db16dbef4392970de32705abc77ee80a902572c8da811dade3564"
dependencies = [
"nu-protocol",
"nu-utils",
@ -1585,9 +1645,9 @@ dependencies = [
[[package]]
name = "nu-protocol"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "038943300ca9de0924fef1c795a7dd16ffc67105629477cf163e8ee6bad95ea6"
checksum = "f1c0e58cbeb46cbfd40156e6f4b9f90e4a77e774ca863fa158867a4726aab1d1"
dependencies = [
"brotli",
"bytes",
@ -1616,18 +1676,18 @@ dependencies = [
"serde_json",
"strum",
"strum_macros",
"thiserror 2.0.12",
"thiserror 2.0.18",
"typetag",
"web-time",
"windows 0.62.2",
"windows",
"windows-sys 0.61.2",
]
[[package]]
name = "nu-system"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46be734cc9b19e09a9665769e14360e13e6978490056ba5c8bfad7dd0537ea83"
checksum = "62fe7847b65edbe362a0fcb67dedfab9fd7370e89c0313f7cb7d0a7ab8f9834b"
dependencies = [
"chrono",
"itertools 0.14.0",
@ -1639,15 +1699,16 @@ dependencies = [
"ntapi",
"procfs",
"sysinfo",
"uucore",
"web-time",
"windows 0.62.2",
"windows",
]
[[package]]
name = "nu-utils"
version = "0.109.1"
version = "0.111.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f8eb43c29cc5bce85f87defdadc2cca964fa434d808af37036a7cb78f3c68e9"
checksum = "df85a8a4bb28c84d5f7c096c02c859ac454dfac59fd0296ab5eb6ed86619219e"
dependencies = [
"byteyarn",
"crossterm",
@ -1668,7 +1729,7 @@ dependencies = [
[[package]]
name = "nu_plugin_image"
version = "0.109.1"
version = "0.111.0"
dependencies = [
"ab_glyph",
"ansi_colours",
@ -1790,18 +1851,18 @@ dependencies = [
[[package]]
name = "objc2-core-foundation"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c10c2894a6fed806ade6027bcd50662746363a9589d3ec9d9bef30a4e4bc166"
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
dependencies = [
"bitflags",
]
[[package]]
name = "objc2-io-kit"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c1c64d6120e51cd86033f67176b1cb66780c2efe34dec55176f77befd93c0a"
checksum = "33fafba39597d6dc1fb709123dfa8289d39406734be322956a69f0931c73bb15"
dependencies = [
"libc",
"objc2-core-foundation",
@ -1831,6 +1892,15 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
[[package]]
name = "os_display"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad5fd71b79026fb918650dde6d125000a233764f1c2f1659a1c71118e33ea08f"
dependencies = [
"unicode-width 0.2.1",
]
[[package]]
name = "os_pipe"
version = "1.2.2"
@ -1982,23 +2052,22 @@ dependencies = [
[[package]]
name = "procfs"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc5b72d8145275d844d4b5f6d4e1eef00c8cd889edb6035c21675d1bb1f45c9f"
checksum = "25485360a54d6861439d60facef26de713b1e126bf015ec8f98239467a2b82f7"
dependencies = [
"bitflags",
"chrono",
"flate2",
"hex",
"procfs-core",
"rustix 0.38.44",
"rustix",
]
[[package]]
name = "procfs-core"
version = "0.17.0"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "239df02d8349b06fc07398a3a1697b06418223b1c7725085e801e7c0fc6a12ec"
checksum = "e6401bf7b6af22f78b563665d15a22e9aef27775b79b149a66ca022468a4e405"
dependencies = [
"bitflags",
"chrono",
@ -2026,9 +2095,9 @@ dependencies = [
[[package]]
name = "pure-rust-locales"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1190fd18ae6ce9e137184f207593877e70f39b015040156b1e05081cdfe3733a"
checksum = "869675ad2d7541aea90c6d88c81f46a7f4ea9af8cd0395d38f11a95126998a0d"
[[package]]
name = "pwd"
@ -2178,7 +2247,7 @@ dependencies = [
"rand 0.9.2",
"rand_chacha 0.9.0",
"simd_helpers",
"thiserror 2.0.12",
"thiserror 2.0.18",
"v_frame",
"wasm-bindgen",
]
@ -2247,23 +2316,23 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac"
dependencies = [
"getrandom 0.2.16",
"libredox",
"thiserror 2.0.12",
"thiserror 2.0.18",
]
[[package]]
name = "ref-cast"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf"
checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d"
dependencies = [
"ref-cast-impl",
]
[[package]]
name = "ref-cast-impl"
version = "1.0.24"
version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7"
checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da"
dependencies = [
"proc-macro2",
"quote",
@ -2327,11 +2396,10 @@ dependencies = [
[[package]]
name = "rmp-serde"
version = "1.3.0"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
checksum = "72f81bee8c8ef9b577d1681a70ebbc962c232461e397b22c208c43c04b67a155"
dependencies = [
"byteorder",
"rmp",
"serde",
]
@ -2343,17 +2411,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
[[package]]
name = "rustix"
version = "0.38.44"
name = "rustc-hash"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.4.15",
"windows-sys 0.59.0",
]
checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d"
[[package]]
name = "rustix"
@ -2364,7 +2425,7 @@ dependencies = [
"bitflags",
"errno",
"libc",
"linux-raw-sys 0.9.4",
"linux-raw-sys",
"windows-sys 0.59.0",
]
@ -2395,6 +2456,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "self_cell"
version = "1.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b12e76d157a900eb52e81bc6e9f3069344290341720e9178cde2407113ac8d89"
[[package]]
name = "semver"
version = "1.0.26"
@ -2433,14 +2500,15 @@ dependencies = [
[[package]]
name = "serde_json"
version = "1.0.140"
version = "1.0.149"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
"serde_core",
"zmij",
]
[[package]]
@ -2574,9 +2642,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]]
name = "strum"
version = "0.26.3"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06"
checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf"
[[package]]
name = "strum_macros"
@ -2643,16 +2711,16 @@ dependencies = [
[[package]]
name = "sysinfo"
version = "0.37.2"
version = "0.38.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16607d5caffd1c07ce073528f9ed972d88db15dd44023fa57142963be3feb11f"
checksum = "92ab6a2f8bfe508deb3c6406578252e491d299cbbf3bc0529ecc3313aee4a52f"
dependencies = [
"libc",
"memchr",
"ntapi",
"objc2-core-foundation",
"objc2-io-kit",
"windows 0.61.3",
"windows",
]
[[package]]
@ -2685,7 +2753,7 @@ version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45c6481c4829e4cc63825e62c49186a34538b7b2750b73b266581ffb612fb5ed"
dependencies = [
"rustix 1.0.7",
"rustix",
"windows-sys 0.59.0",
]
@ -2710,11 +2778,11 @@ dependencies = [
[[package]]
name = "thiserror"
version = "2.0.12"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4"
dependencies = [
"thiserror-impl 2.0.12",
"thiserror-impl 2.0.18",
]
[[package]]
@ -2730,9 +2798,9 @@ dependencies = [
[[package]]
name = "thiserror-impl"
version = "2.0.12"
version = "2.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5"
dependencies = [
"proc-macro2",
"quote",
@ -2793,12 +2861,32 @@ dependencies = [
"time-core",
]
[[package]]
name = "tinystr"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869"
dependencies = [
"displaydoc",
"serde_core",
"zerovec",
]
[[package]]
name = "ttf-parser"
version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2df906b07856748fa3f6e0ad0cbaa047052d4a7dd609e231c4f72cee8c36f31"
[[package]]
name = "type-map"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb30dbbd9036155e74adad6812e9898d03ec374946234fbcebd5dfc7b9187b90"
dependencies = [
"rustc-hash 2.1.1",
]
[[package]]
name = "typeid"
version = "1.0.3"
@ -2836,10 +2924,28 @@ dependencies = [
]
[[package]]
name = "unicase"
version = "2.8.1"
name = "unic-langid"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
checksum = "a28ba52c9b05311f4f6e62d5d9d46f094bd6e84cb8df7b3ef952748d752a7d05"
dependencies = [
"unic-langid-impl",
]
[[package]]
name = "unic-langid-impl"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dce1bf08044d4b7a94028c93786f8566047edc11110595914de93362559bc658"
dependencies = [
"tinystr",
]
[[package]]
name = "unicase"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142"
[[package]]
name = "unicode-ident"
@ -2877,6 +2983,35 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]]
name = "uucore"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b157ba598d7f7ed06f6dbc62999edb9d730b4d3fb58e503d8ad6d5fbe1e04391"
dependencies = [
"clap",
"fluent",
"fluent-bundle",
"fluent-syntax",
"libc",
"nix",
"os_display",
"thiserror 2.0.18",
"unic-langid",
"uucore_procs",
"wild",
]
[[package]]
name = "uucore_procs"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "daa291a52608ac5a2f8539e119666e021baa6b8c01f22f02ed201bbae54cbbc0"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "v_frame"
version = "0.3.9"
@ -3018,6 +3153,15 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
[[package]]
name = "wild"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3131afc8c575281e1e80f36ed6a092aa502c08b18ed7524e86fbbb12bb410e1"
dependencies = [
"glob",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -3049,38 +3193,16 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.61.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893"
dependencies = [
"windows-collections 0.2.0",
"windows-core 0.61.2",
"windows-future 0.2.1",
"windows-link 0.1.3",
"windows-numerics 0.2.0",
]
[[package]]
name = "windows"
version = "0.62.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
dependencies = [
"windows-collections 0.3.2",
"windows-collections",
"windows-core 0.62.2",
"windows-future 0.3.2",
"windows-numerics 0.3.1",
]
[[package]]
name = "windows-collections"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8"
dependencies = [
"windows-core 0.61.2",
"windows-future",
"windows-numerics",
]
[[package]]
@ -3118,17 +3240,6 @@ dependencies = [
"windows-strings 0.5.1",
]
[[package]]
name = "windows-future"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
"windows-threading 0.1.0",
]
[[package]]
name = "windows-future"
version = "0.3.2"
@ -3137,7 +3248,7 @@ checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
dependencies = [
"windows-core 0.62.2",
"windows-link 0.2.1",
"windows-threading 0.2.1",
"windows-threading",
]
[[package]]
@ -3174,16 +3285,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]]
name = "windows-numerics"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1"
dependencies = [
"windows-core 0.61.2",
"windows-link 0.1.3",
]
[[package]]
name = "windows-numerics"
version = "0.3.1"
@ -3298,15 +3399,6 @@ dependencies = [
"windows_x86_64_msvc 0.53.0",
]
[[package]]
name = "windows-threading"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
dependencies = [
"windows-link 0.1.3",
]
[[package]]
name = "windows-threading"
version = "0.2.1"
@ -3468,6 +3560,28 @@ dependencies = [
"syn 2.0.104",
]
[[package]]
name = "zerofrom"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5"
[[package]]
name = "zerovec"
version = "0.11.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002"
dependencies = [
"serde",
"zerofrom",
]
[[package]]
name = "zmij"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa"
[[package]]
name = "zstd"
version = "0.13.3"

View File

@ -11,8 +11,8 @@ vte = "0.15.0"
lazy_static = "1.5.0"
slog-term = "2.9.2"
slog-async = "2.8.0"
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
[dependencies.clap]
features = ["derive"]
@ -52,4 +52,4 @@ license = "MIT"
name = "nu_plugin_image"
readme = "README.md"
repository = "https://github.com/FMotalleb/nu_plugin_image"
version = "0.109.1"
version = "0.111.0"

View File

@ -19,7 +19,8 @@ The `to png` command converts an ANSI string into a PNG image. Customizable font
#### 📌 Usage
```bash
```nushell
bash
> to png {flags} (output-path)
```
@ -55,7 +56,8 @@ The `to png` command converts an ANSI string into a PNG image. Customizable font
#### 📊 Example: Convert ANSI String to PNG with Custom Theme
```bash
```nushell
bash
> to png --theme "xterm" --custom-theme-fg "#FF00FF" --custom-theme-bg "#00000000" output.png
```
@ -67,7 +69,8 @@ The `from png` command converts an image into its corresponding ANSI text repres
#### 📌 Usage
```bash
```nushell
bash
> from png {flags}
```
@ -80,7 +83,8 @@ The `from png` command converts an image into its corresponding ANSI text repres
#### 📊 Example: Convert PNG Image to ANSI Text
```bash
```nushell
bash
> from png --width 80 --height 20 image.png
```
@ -92,14 +96,16 @@ The `from png` command converts an image into its corresponding ANSI text repres
This method automatically handles dependencies and features.
```bash
```nushell
bash
git clone https://github.com/FMotalleb/nu_plugin_image.git
nupm install --path nu_plugin_image -f
```
### 🛠️ Manual Compilation
```bash
```nushell
bash
git clone https://github.com/FMotalleb/nu_plugin_image.git
cd nu_plugin_image
cargo build -r
@ -108,8 +114,8 @@ plugin add target/release/nu_plugin_image
### 📦 Install via Cargo (using git)
```bash
```rust
bash
cargo install --git https://github.com/FMotalleb/nu_plugin_image.git
plugin add ~/.cargo/bin/nu_plugin_image
```

View File

@ -73,7 +73,9 @@ fn load_u32(call: &EvaluatedCall, flag_name: &str) -> Result<u32, LabeledError>
.map_err(|e| make_params_err(e.to_string(), call.head))?;
int_val
.try_into()
.map_err(|err: std::num::TryFromIntError| make_params_err(err.to_string(), call.head))
.map_err(|err: std::num::TryFromIntError| {
make_params_err(err.to_string(), call.head)
})
}
_ => Err(make_params_err(
format!("value of `{flag_name}` is not an integer"),

@ -1 +1 @@
Subproject commit 11216da7174f88a7d5b3ce04fea3cd14bcde8d8c
Subproject commit 97e7365a5a917cca6559ea8e66063a4242dc2605

697
nu_plugin_kms/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
[package]
name = "nu_plugin_kms"
version = "0.1.0"
version = "0.111.0"
authors = ["Jesus Perez <jesus@librecloud.online>"]
edition = "2021"
description = "Nushell plugin for KMS operations (RustyVault, Age, Cosmian)"
@ -8,8 +8,8 @@ repository = "https://github.com/provisioning/nu_plugin_kms"
license = "MIT"
[dependencies]
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
rusty_vault = "0.2.1"
age = "0.11"
base64 = "0.22"
@ -32,6 +32,5 @@ default-features = false
version = "1.48"
features = ["full"]
[dev-dependencies.nu-plugin-test-support]
version = "0.109.1"
path = "../nushell/crates/nu-plugin-test-support"
[dev-dependencies]
nu-plugin-test-support = "0.111.0"

View File

@ -1,37 +0,0 @@
[package]
name = "nu_plugin_kms"
version = "0.1.0"
authors = ["Jesus Perez <jesus@librecloud.online>"]
edition = "2021"
description = "Nushell plugin for KMS operations (RustyVault, Age, Cosmian)"
repository = "https://github.com/provisioning/nu_plugin_kms"
license = "MIT"
[dependencies]
nu-plugin = "0.109.1"
nu-protocol = "0.109.1"
rusty_vault = "0.2.1"
age = "0.11"
base64 = "0.22"
serde_json = "1.0"
tempfile = "3.23"
[dependencies.serde]
version = "1.0"
features = ["derive"]
[dependencies.reqwest]
version = "0.12"
features = [
"json",
"rustls-tls",
]
default-features = false
[dependencies.tokio]
version = "1.48"
features = ["full"]
[dev-dependencies.nu-plugin-test-support]
version = "0.109.0"
path = "../nushell/crates/nu-plugin-test-support"

View File

@ -18,7 +18,8 @@ Nushell plugin for KMS operations with multiple backends.
## Installation
```bash
```nushell
bash
cargo build --release
plugin add target/release/nu_plugin_kms
```

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -182,7 +182,8 @@ mod tests {
#[test]
fn test_kms_error_with_source() {
let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "key file not found");
let error = KmsError::with_source(KmsErrorKind::KeyNotFound, "failed to load key", io_error);
let error =
KmsError::with_source(KmsErrorKind::KeyNotFound, "failed to load key", io_error);
assert!(error.to_string().contains("caused by"));
}

View File

@ -209,8 +209,9 @@ pub fn encrypt_age(data: &[u8], recipient_str: &str) -> Result<String, String> {
.parse::<age::x25519::Recipient>()
.map_err(|e| format!("Invalid Age recipient: {}", e))?;
let encryptor = age::Encryptor::with_recipients(std::iter::once(&recipient as &dyn age::Recipient))
.map_err(|_| "Failed to create Age encryptor".to_string())?;
let encryptor =
age::Encryptor::with_recipients(std::iter::once(&recipient as &dyn age::Recipient))
.map_err(|_| "Failed to create Age encryptor".to_string())?;
let mut encrypted = vec![];
let mut writer = encryptor
@ -513,9 +514,7 @@ pub async fn decrypt_aws_kms(_key_id: &str, ciphertext: &str) -> Result<Vec<u8>,
.await
.map_err(|e| format!("JSON parse error: {}", e))?;
let plaintext_b64 = result["Plaintext"]
.as_str()
.ok_or("Missing Plaintext")?;
let plaintext_b64 = result["Plaintext"].as_str().ok_or("Missing Plaintext")?;
general_purpose::STANDARD
.decode(plaintext_b64)
@ -523,7 +522,10 @@ pub async fn decrypt_aws_kms(_key_id: &str, ciphertext: &str) -> Result<Vec<u8>,
}
/// Generate data key using AWS KMS
pub async fn generate_data_key_aws(key_id: &str, key_spec: &str) -> Result<(String, String), String> {
pub async fn generate_data_key_aws(
key_id: &str,
key_spec: &str,
) -> Result<(String, String), String> {
let client = reqwest::Client::new();
let kms_url = std::env::var("AWS_KMS_ENDPOINT")
@ -649,7 +651,10 @@ pub async fn generate_data_key_vault(
};
let response = client
.post(format!("{}/v1/transit/datakey/plaintext/{}", addr, key_name))
.post(format!(
"{}/v1/transit/datakey/plaintext/{}",
addr, key_name
))
.header("X-Vault-Token", token)
.json(&serde_json::json!({
"bits": bits,
@ -685,16 +690,12 @@ pub fn check_backend_available(backend_name: &str) -> bool {
"rustyvault" => {
std::env::var("RUSTYVAULT_ADDR").is_ok() && std::env::var("RUSTYVAULT_TOKEN").is_ok()
}
"age" => {
std::env::var("AGE_RECIPIENT").is_ok() || std::env::var("AGE_IDENTITY").is_ok()
}
"age" => std::env::var("AGE_RECIPIENT").is_ok() || std::env::var("AGE_IDENTITY").is_ok(),
"aws" => {
std::env::var("AWS_ACCESS_KEY_ID").is_ok()
&& std::env::var("AWS_SECRET_ACCESS_KEY").is_ok()
}
"vault" => {
std::env::var("VAULT_ADDR").is_ok() && std::env::var("VAULT_TOKEN").is_ok()
}
"vault" => std::env::var("VAULT_ADDR").is_ok() && std::env::var("VAULT_TOKEN").is_ok(),
"cosmian" => std::env::var("KMS_HTTP_URL").is_ok(),
_ => false,
}

View File

@ -169,14 +169,15 @@ impl SimplePluginCommand for KmsEncrypt {
helpers::encrypt_age(data.as_bytes(), recipient)
.map_err(|e| LabeledError::new(e))?
}
helpers::Backend::AwsKms { ref key_id } => {
runtime.block_on(async {
helpers::encrypt_aws_kms(key_id, data.as_bytes())
.await
.map_err(|e| LabeledError::new(e))
})?
}
helpers::Backend::Vault { ref addr, ref token } => {
helpers::Backend::AwsKms { ref key_id } => runtime.block_on(async {
helpers::encrypt_aws_kms(key_id, data.as_bytes())
.await
.map_err(|e| LabeledError::new(e))
})?,
helpers::Backend::Vault {
ref addr,
ref token,
} => {
let key_name = key.unwrap_or_else(|| "provisioning-main".to_string());
runtime.block_on(async {
helpers::encrypt_vault(addr, token, &key_name, data.as_bytes())
@ -321,14 +322,15 @@ impl SimplePluginCommand for KmsDecrypt {
})?;
helpers::decrypt_age(&encrypted, identity_path).map_err(|e| LabeledError::new(e))?
}
helpers::Backend::AwsKms { ref key_id } => {
runtime.block_on(async {
helpers::decrypt_aws_kms(key_id, &encrypted)
.await
.map_err(|e| LabeledError::new(e))
})?
}
helpers::Backend::Vault { ref addr, ref token } => {
helpers::Backend::AwsKms { ref key_id } => runtime.block_on(async {
helpers::decrypt_aws_kms(key_id, &encrypted)
.await
.map_err(|e| LabeledError::new(e))
})?,
helpers::Backend::Vault {
ref addr,
ref token,
} => {
let key_name = key.unwrap_or_else(|| "provisioning-main".to_string());
runtime.block_on(async {
helpers::decrypt_vault(addr, token, &key_name, &encrypted)
@ -463,20 +465,19 @@ impl SimplePluginCommand for KmsGenerateKey {
.map(|(secret, public)| (secret, public))
.map_err(|e| LabeledError::new(e))?
}
helpers::Backend::AwsKms { ref key_id } => {
runtime.block_on(async {
helpers::generate_data_key_aws(key_id, &key_spec)
.await
.map_err(|e| LabeledError::new(e))
})?
}
helpers::Backend::Vault { ref addr, ref token } => {
runtime.block_on(async {
helpers::generate_data_key_vault(addr, token, "provisioning-main", &key_spec)
.await
.map_err(|e| LabeledError::new(e))
})?
}
helpers::Backend::AwsKms { ref key_id } => runtime.block_on(async {
helpers::generate_data_key_aws(key_id, &key_spec)
.await
.map_err(|e| LabeledError::new(e))
})?,
helpers::Backend::Vault {
ref addr,
ref token,
} => runtime.block_on(async {
helpers::generate_data_key_vault(addr, token, "provisioning-main", &key_spec)
.await
.map_err(|e| LabeledError::new(e))
})?,
helpers::Backend::HttpFallback {
ref backend_name,
ref url,
@ -560,12 +561,8 @@ impl SimplePluginCommand for KmsStatus {
format!("recipient: {}, identity: {}", recipient, identity_status),
)
}
helpers::Backend::AwsKms { ref key_id } => {
("aws", true, format!("key_id: {}", key_id))
}
helpers::Backend::Vault { ref addr, .. } => {
("vault", true, format!("addr: {}", addr))
}
helpers::Backend::AwsKms { ref key_id } => ("aws", true, format!("key_id: {}", key_id)),
helpers::Backend::Vault { ref addr, .. } => ("vault", true, format!("addr: {}", addr)),
helpers::Backend::HttpFallback {
ref backend_name,
ref url,
@ -624,11 +621,31 @@ impl SimplePluginCommand for KmsListBackends {
_input: &Value,
) -> Result<Value, LabeledError> {
let backends = vec![
("rustyvault", "RustyVault Transit backend", "RUSTYVAULT_ADDR, RUSTYVAULT_TOKEN"),
("age", "Age file-based encryption", "AGE_RECIPIENT, AGE_IDENTITY"),
("aws", "AWS Key Management Service", "AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION"),
("vault", "HashiCorp Vault Transit", "VAULT_ADDR, VAULT_TOKEN"),
("cosmian", "Cosmian privacy-preserving encryption", "KMS_HTTP_URL"),
(
"rustyvault",
"RustyVault Transit backend",
"RUSTYVAULT_ADDR, RUSTYVAULT_TOKEN",
),
(
"age",
"Age file-based encryption",
"AGE_RECIPIENT, AGE_IDENTITY",
),
(
"aws",
"AWS Key Management Service",
"AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION",
),
(
"vault",
"HashiCorp Vault Transit",
"VAULT_ADDR, VAULT_TOKEN",
),
(
"cosmian",
"Cosmian privacy-preserving encryption",
"KMS_HTTP_URL",
),
];
let backend_values: Vec<Value> = backends

View File

@ -24,11 +24,26 @@ fn test_kms_error_with_source() {
#[test]
fn test_kms_error_kind_display() {
assert_eq!(KmsErrorKind::BackendNotAvailable.to_string(), "backend not available");
assert_eq!(KmsErrorKind::EncryptionFailed.to_string(), "encryption failed");
assert_eq!(KmsErrorKind::DecryptionFailed.to_string(), "decryption failed");
assert_eq!(KmsErrorKind::KeyGenerationFailed.to_string(), "key generation failed");
assert_eq!(KmsErrorKind::InvalidKeySpec.to_string(), "invalid key specification");
assert_eq!(
KmsErrorKind::BackendNotAvailable.to_string(),
"backend not available"
);
assert_eq!(
KmsErrorKind::EncryptionFailed.to_string(),
"encryption failed"
);
assert_eq!(
KmsErrorKind::DecryptionFailed.to_string(),
"decryption failed"
);
assert_eq!(
KmsErrorKind::KeyGenerationFailed.to_string(),
"key generation failed"
);
assert_eq!(
KmsErrorKind::InvalidKeySpec.to_string(),
"invalid key specification"
);
assert_eq!(KmsErrorKind::NetworkError.to_string(), "network error");
}
@ -71,10 +86,7 @@ fn test_backend_new_age_invalid() {
fn test_backend_http_fallback() {
let backend = helpers::Backend::new_http_fallback("cosmian", "http://localhost:8081");
match backend {
helpers::Backend::HttpFallback {
backend_name,
url,
} => {
helpers::Backend::HttpFallback { backend_name, url } => {
assert_eq!(backend_name, "cosmian");
assert_eq!(url, "http://localhost:8081");
}

View File

@ -7,7 +7,8 @@
### 1. Verify Binary Exists
```bash
```nushell
bash
cd /Users/Akasha/project-provisioning/provisioning/core/plugins/nushell-plugins/nu_plugin_kms
# Check binary exists
@ -19,7 +20,8 @@ ls -lh target/release/nu_plugin_kms
### 2. Register Plugin with Nushell
```bash
```nushell
bash
# Register plugin
nu -c "plugin add target/release/nu_plugin_kms"
@ -35,7 +37,8 @@ nu -c "plugin list | where name =~ kms"
### 3. Test Plugin Help
```bash
```nushell
bash
# Check command help
nu -c "kms encrypt --help"
nu -c "kms decrypt --help"
@ -49,7 +52,8 @@ nu -c "kms status --help"
#### Setup
```bash
```nushell
bash
# Install Age (if not already installed)
brew install age # macOS
# or
@ -68,7 +72,8 @@ echo "Age Identity: $AGE_IDENTITY"
#### Test Encryption
```bash
```nushell
bash
# Test 1: Encrypt with Age
nu -c "kms encrypt 'Hello, Age!' --backend age --key $AGE_RECIPIENT" > /tmp/encrypted.txt
@ -80,7 +85,8 @@ cat /tmp/encrypted.txt
#### Test Decryption
```bash
```nushell
bash
# Test 2: Decrypt with Age
nu -c "kms decrypt '$(cat /tmp/encrypted.txt)' --backend age --key $AGE_IDENTITY"
@ -89,7 +95,8 @@ nu -c "kms decrypt '$(cat /tmp/encrypted.txt)' --backend age --key $AGE_IDENTITY
#### Test Key Generation
```bash
```nushell
bash
# Test 3: Generate new Age key pair
nu -c "kms generate-key --backend age"
@ -102,7 +109,8 @@ nu -c "kms generate-key --backend age"
#### Test Auto-Detection
```bash
```nushell
bash
# Test 4: Auto-detect Age backend
nu -c "kms status"
@ -118,7 +126,8 @@ nu -c "kms status"
#### Setup
```bash
```nushell
bash
# Start RustyVault in Docker
docker run -d --name rustyvault \
-p 8200:8200 \
@ -143,7 +152,8 @@ docker exec rustyvault \
#### Test Encryption
```bash
```nushell
bash
# Test 1: Encrypt with RustyVault
nu -c "kms encrypt 'Secret data!' --backend rustyvault --key provisioning-main"
@ -152,7 +162,8 @@ nu -c "kms encrypt 'Secret data!' --backend rustyvault --key provisioning-main"
#### Test Decryption
```bash
```nushell
bash
# Test 2: Encrypt and then decrypt
ENCRYPTED=$(nu -c "kms encrypt 'RustyVault test' --backend rustyvault --key provisioning-main")
@ -163,7 +174,8 @@ nu -c "kms decrypt '$ENCRYPTED' --backend rustyvault --key provisioning-main"
#### Test Key Generation
```bash
```nushell
bash
# Test 3: Generate AES256 data key
nu -c "kms generate-key --backend rustyvault --spec AES256"
@ -176,7 +188,8 @@ nu -c "kms generate-key --backend rustyvault --spec AES256"
#### Test Status
```bash
```nushell
bash
# Test 4: Check RustyVault status
nu -c "kms status"
@ -190,7 +203,8 @@ nu -c "kms status"
#### Cleanup
```bash
```nushell
bash
# Stop and remove RustyVault container
docker stop rustyvault
docker rm rustyvault
@ -200,7 +214,8 @@ docker rm rustyvault
#### Setup (Mock Server)
```bash
```nushell
bash
# Create simple mock KMS server with Python
cat > /tmp/mock_kms_server.py << 'EOF'
#!/usr/bin/env python3
@ -262,7 +277,8 @@ export KMS_HTTP_BACKEND="mock"
#### Test HTTP Backend
```bash
```nushell
bash
# Test 1: Encrypt
nu -c "kms encrypt 'HTTP test data' --backend mock"
@ -279,7 +295,8 @@ nu -c "kms status"
#### Cleanup
```bash
```nushell
bash
# Stop mock server
kill $MOCK_SERVER_PID
```
@ -288,7 +305,8 @@ kill $MOCK_SERVER_PID
### Test Auto-Detection Priority
```bash
```nushell
bash
# Test 1: RustyVault has priority
export RUSTYVAULT_ADDR="http://localhost:8200"
export RUSTYVAULT_TOKEN="test-token"
@ -313,7 +331,8 @@ nu -c "kms status"
### Test Error Handling
```bash
```nushell
bash
# Test 1: Missing required flags
nu -c "kms encrypt 'data' --backend age"
# Expected: Error about missing --key recipient
@ -334,7 +353,8 @@ nu -c "kms generate-key --backend rustyvault --spec INVALID"
### Test Binary Data
```bash
```nushell
bash
# Test handling of non-UTF8 data
dd if=/dev/urandom bs=1024 count=1 | base64 > /tmp/random.b64
@ -356,7 +376,8 @@ fi
### Age Performance
```bash
```nushell
bash
# Benchmark encryption (1000 iterations)
time for i in {1..1000}; do
nu -c "kms encrypt 'test data' --backend age --key $AGE_RECIPIENT" > /dev/null
@ -367,7 +388,8 @@ done
### RustyVault Performance
```bash
```rust
bash
# Benchmark encryption (1000 iterations)
time for i in {1..1000}; do
nu -c "kms encrypt 'test data' --backend rustyvault --key provisioning-main" > /dev/null
@ -378,7 +400,8 @@ done
### Memory Usage
```bash
```nushell
bash
# Monitor memory during operations
while true; do
nu -c "kms encrypt 'test data' --backend age --key $AGE_RECIPIENT" > /dev/null
@ -392,17 +415,20 @@ done
## Verification Checklist
### Compilation
- [x] `cargo check` passes
- [x] `cargo build --release` succeeds
- [x] Binary created in `target/release/nu_plugin_kms`
- [x] File size reasonable (< 50MB)
### Plugin Registration
- [ ] Plugin registers with Nushell
- [ ] All 4 commands visible in `plugin list`
- [ ] Help text accessible for each command
### Age Backend
- [ ] Encryption works with recipient
- [ ] Decryption works with identity file
- [ ] Key generation produces valid key pair
@ -410,6 +436,7 @@ done
- [ ] Auto-detection works when env vars set
### RustyVault Backend
- [ ] Encryption works with Transit engine
- [ ] Decryption works correctly
- [ ] Data key generation works
@ -417,6 +444,7 @@ done
- [ ] Auto-detection works when env vars set
### HTTP Fallback
- [ ] Encryption works with HTTP service
- [ ] Decryption works correctly
- [ ] Data key generation works
@ -424,12 +452,14 @@ done
- [ ] Auto-detection works as fallback
### Error Handling
- [ ] Missing flags produce clear errors
- [ ] Invalid inputs rejected gracefully
- [ ] Network errors handled properly
- [ ] Missing env vars reported clearly
### Integration
- [ ] Auto-detection priority correct
- [ ] Multiple backends can coexist
- [ ] Environment switching works
@ -438,21 +468,25 @@ done
## Success Criteria
✅ **Basic Functionality**
- All backends encrypt and decrypt successfully
- Key generation works for all backends
- Status command reports correctly
✅ **Robustness**
- Error messages are clear and actionable
- No panics or crashes
- Memory usage is stable
✅ **Performance**
- Operations complete in reasonable time
- No memory leaks
- Concurrent operations work
✅ **Usability**
- Auto-detection works as expected
- Environment configuration is straightforward
- Help text is clear
@ -461,7 +495,8 @@ done
### Plugin Not Loading
```bash
```nushell
bash
# Check plugin is registered
nu -c "plugin list" | grep kms
@ -474,7 +509,8 @@ nu -c "plugin list --version"
### Environment Variables Not Working
```bash
```nushell
bash
# Check env vars are set
env | grep -E '(RUSTYVAULT|AGE|KMS)'
@ -484,7 +520,8 @@ bash -c 'export AGE_RECIPIENT=...; nu -c "kms status"'
### Compilation Errors
```bash
```nushell
bash
# Clean build
cargo clean

2242
nu_plugin_mcp/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

27
nu_plugin_mcp/Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[[bin]]
name = "nu_plugin_mcp"
path = "src/main.rs"
[lib]
name = "nu_plugin_mcp"
path = "src/lib.rs"
[package]
name = "nu_plugin_mcp"
version = "0.111.0"
edition = "2021"
description = "Nushell plugin for MCP server interaction — connect, discover, and call provisioning tools"
authors = ["Provisioning Team"]
license = "MIT"
[dependencies]
serde_json = "1.0"
thiserror = "2.0"
interprocess = "^2.3.1"
nu-plugin = "0.111.0"
nu-protocol = "0.111.0"
[profile.release]
opt-level = 3
lto = true
codegen-units = 1

View File

@ -0,0 +1,12 @@
#!/usr/bin/env nu
# Validate infrastructure config via MCP and display results
mcp connect provisioning-mcp-server --provisioning-path /opt/provisioning
let result = mcp tool call guidance_validate_config_file --payload {
config_path: "provisioning/config/workspace.ncl"
}
print $result
mcp disconnect

View File

@ -0,0 +1,12 @@
#!/usr/bin/env nu
# Search provisioning documentation via RAG
mcp connect provisioning-mcp-server --provisioning-path /opt/provisioning
let docs = mcp tool call guidance_find_docs --payload {
query: "nickel validation type-safe configuration"
}
print $docs
mcp disconnect

View File

@ -0,0 +1,8 @@
#!/usr/bin/env nu
# Discover all available MCP tools and display as a table
mcp connect provisioning-mcp-server --provisioning-path /opt/provisioning
mcp tools list | select name description | sort-by name | table
mcp disconnect

View File

@ -0,0 +1,192 @@
use crate::McpPlugin;
use nu_plugin::{EvaluatedCall, SimplePluginCommand};
use nu_protocol::{
Category, Example, LabeledError, Record, Signature, Span, SyntaxShape, Type, Value,
};
use serde_json::Value as Json;
#[derive(Debug)]
pub struct McpToolCall;
impl SimplePluginCommand for McpToolCall {
type Plugin = McpPlugin;
fn name(&self) -> &str {
"mcp tool call"
}
fn description(&self) -> &str {
"Call a tool on the connected MCP server"
}
fn signature(&self) -> Signature {
Signature::build("mcp tool call")
.input_output_type(Type::Nothing, Type::Any)
.required(
"name",
SyntaxShape::String,
"Tool name (e.g. provision_status)",
)
.named(
"payload",
SyntaxShape::Record(vec![]),
"Arguments to pass to the tool as a record",
Some('a'),
)
.category(Category::Custom("provisioning".into()))
}
fn examples(&self) -> Vec<Example<'_>> {
vec![
Example {
example: "mcp tool call provision_status",
description: "Get provisioning infrastructure status",
result: None,
},
Example {
example: "mcp tool call provision_query --payload {query: \"list all servers\"}",
description: "Query infrastructure using natural language",
result: None,
},
Example {
example:
"mcp tool call guidance_find_docs --payload {query: \"nickel validation\"}",
description: "Find relevant documentation",
result: None,
},
]
}
fn run(
&self,
plugin: &McpPlugin,
_engine: &nu_plugin::EngineInterface,
call: &EvaluatedCall,
_input: &Value,
) -> Result<Value, LabeledError> {
let tool_name: String = call.req(0)?;
let payload_val: Option<Value> = call.get_flag("payload")?;
let span = call.head;
let arguments = match payload_val {
Some(v) => nu_record_to_json(&v)
.map_err(|e| LabeledError::new(format!("payload conversion: {e}")))?,
None => Json::Object(Default::default()),
};
let mut guard = plugin
.session
.lock()
.map_err(|_| LabeledError::new("session mutex poisoned"))?;
let session = guard
.as_mut()
.ok_or_else(|| LabeledError::new("not connected — run `mcp connect <binary>` first"))?;
let result = session
.tool_call(&tool_name, arguments)
.map_err(|e| LabeledError::new(e.to_string()))?;
Ok(mcp_result_to_nu(result, span))
}
}
/// Convert MCP tool result to a Nu value.
/// MCP spec: result = { content: [{type: "text", text: "..."}, ...], isError?: bool }
fn mcp_result_to_nu(result: Json, span: Span) -> Value {
let is_error = result
.get("isError")
.and_then(|v| v.as_bool())
.unwrap_or(false);
let text = result
.get("content")
.and_then(|c| c.as_array())
.and_then(|arr| arr.first())
.and_then(|item| item.get("text"))
.and_then(|t| t.as_str())
.unwrap_or("")
.to_owned();
if is_error {
let mut rec = Record::new();
rec.push("error", Value::bool(true, span));
rec.push("message", Value::string(text, span));
Value::record(rec, span)
} else {
// Try to parse text as JSON for richer output; fall back to plain string
if let Ok(json_val) = serde_json::from_str::<Json>(&text) {
json_to_nu_value(json_val, span)
} else {
Value::string(text, span)
}
}
}
fn json_to_nu_value(v: Json, span: Span) -> Value {
match v {
Json::Null => Value::nothing(span),
Json::Bool(b) => Value::bool(b, span),
Json::Number(n) => {
if let Some(i) = n.as_i64() {
Value::int(i, span)
} else {
Value::float(n.as_f64().unwrap_or(0.0), span)
}
}
Json::String(s) => Value::string(s, span),
Json::Array(arr) => {
let vals = arr.into_iter().map(|v| json_to_nu_value(v, span)).collect();
Value::list(vals, span)
}
Json::Object(map) => {
let mut rec = Record::new();
for (k, v) in map {
rec.push(k, json_to_nu_value(v, span));
}
Value::record(rec, span)
}
}
}
fn nu_record_to_json(val: &Value) -> Result<Json, String> {
match val {
Value::Nothing { .. } => Ok(Json::Object(Default::default())),
Value::Record { val, .. } => {
let mut map = serde_json::Map::new();
for (k, v) in val.iter() {
map.insert(k.to_owned(), nu_value_to_json(v)?);
}
Ok(Json::Object(map))
}
_ => Err(format!(
"payload must be a record, got {:?}",
val.get_type()
)),
}
}
fn nu_value_to_json(val: &Value) -> Result<Json, String> {
match val {
Value::Nothing { .. } => Ok(Json::Null),
Value::Bool { val, .. } => Ok(Json::Bool(*val)),
Value::Int { val, .. } => Ok(Json::Number((*val).into())),
Value::Float { val, .. } => serde_json::Number::from_f64(*val)
.map(Json::Number)
.ok_or_else(|| format!("non-finite float: {val}")),
Value::String { val, .. } => Ok(Json::String(val.clone())),
Value::List { vals, .. } => {
let arr: Result<Vec<_>, _> = vals.iter().map(nu_value_to_json).collect();
Ok(Json::Array(arr?))
}
Value::Record { val, .. } => {
let mut map = serde_json::Map::new();
for (k, v) in val.iter() {
map.insert(k.to_owned(), nu_value_to_json(v)?);
}
Ok(Json::Object(map))
}
other => Ok(Json::String(
other.to_expanded_string(", ", &Default::default()),
)),
}
}

Some files were not shown because too many files have changed in this diff Show More