#!/usr/bin/env nu # Distribution Plugin Installer - Install & Register from Manifest # # Reads a distribution manifest (JSON) and installs/registers selected plugins. # Plugins are copied to ~/.local/bin/ and registered with Nushell. # # Usage: # install_from_manifest.nu # Uses DISTRIBUTION_MANIFEST.json # install_from_manifest.nu --manifest manifest.json # install_from_manifest.nu --list # List available plugins # install_from_manifest.nu --all # Install all plugins # install_from_manifest.nu --preset essential # Install preset # install_from_manifest.nu --select auth kms # Install specific plugins # install_from_manifest.nu --check # Dry-run def main [ --manifest: string = "DISTRIBUTION_MANIFEST.json" # Path to manifest file --list # List available plugins only --all # Install all plugins --preset: string # Use preset: essential, development, full --select: list # Select specific plugins --check (-c) # Dry-run, don't make changes --install-only # Only install, don't register --register-only # Only register, skip install ] { log_info "šŸ“¦ Nushell Distribution Plugin Installer" log_info "==================================================================" # Step 1: Load manifest log_info $"\nšŸ“‹ Step 1: Loading manifest ($manifest)..." let manifest_data = load_manifest $manifest if $manifest_data == null { return } let plugins = $manifest_data.plugins if ($plugins | length) == 0 { log_error "No plugins in manifest!" return } log_success $"Loaded ($plugins | length) available plugin\(s\)" # List mode? if $list { display_plugin_list $plugins return } # Step 2: Select plugins log_info "\nšŸŽÆ Step 2: Selecting plugins..." let selected = if ($preset | is-not-empty) { get_preset_plugins $plugins $preset } else if ($select | length) > 0 { filter_by_name $plugins $select } else if $all { $plugins } else { interactive_select $plugins } if ($selected | length) == 0 { log_warn "No plugins selected" return } # Step 3: Show selection log_info "" log_success $"Selected: ($selected | length) plugin\(s\)" for plugin in $selected { log_info $" • ($plugin.name) - ($plugin.purpose)" } # Step 4: Dry-run? if $check { log_info "\nāœ… DRY RUN - No changes made" return } # Step 5: Confirm print "" let response = try { input "Proceed with installation? (yes/no): " } catch { "yes" } if $response != "yes" { log_info "Cancelled" return } # Step 6: Install if not $register_only { log_info "\nšŸ“¦ Step 3: Installing plugins..." install_plugins $selected $manifest_data.source_directory } # Step 7: Register if not $install_only { log_info "\nšŸ”Œ Step 4: Registering with Nushell..." register_plugins $selected } # Summary log_info "\n==================================================================" log_success "āœ… Complete!" log_info "" log_info "Next steps:" log_info " 1. Restart Nushell: exit && nu" log_info " 2. Verify: nu -c 'plugin list'" } # Load manifest from JSON file def load_manifest [path: string]: nothing -> record { if not ($path | path exists) { log_error $"Manifest not found: ($path)" return null } try { let content = open $path return $content } catch {|err| log_error $"Failed to load manifest: ($err.msg)" return null } } # Display plugin list def display_plugin_list [plugins: list] { print "" log_success $"Available plugins: ($plugins | length)" print "" for plugin in $plugins { print $" āœ“ ($plugin.name)" print $" ($plugin.purpose)" print "" } } # Get preset plugins def get_preset_plugins [plugins: list, preset: string]: nothing -> list { match $preset { "essential" => { $plugins | where {|p| $p.name in ["nu_plugin_auth", "nu_plugin_kms", "nu_plugin_orchestrator", "nu_plugin_kcl", "nu_plugin_tera"] } } "development" => { $plugins | where {|p| $p.name in ["nu_plugin_auth", "nu_plugin_kms", "nu_plugin_orchestrator", "nu_plugin_kcl", "nu_plugin_tera", "nu_plugin_highlight", "nu_plugin_image", "nu_plugin_clipboard"] } } "full" => { $plugins } _ => { log_error $"Unknown preset: ($preset)" log_info "Available: essential, development, full" [] } } } # Filter by name def filter_by_name [plugins: list, names: list]: nothing -> list { $plugins | where {|p| if $p.name in $names { true } else { ($p.name | str replace "^nu_plugin_" "") in $names } } } # Interactive select def interactive_select [plugins: list]: nothing -> list { print "" log_info "Available presets:" log_info " 1. Essential (5 core plugins)" log_info " 2. Development (8 plugins)" log_info " 3. All ($($plugins | length) plugins)" print "" let choice = try { input "Select (1-3): " } catch { "1" } match $choice { "1" => { get_preset_plugins $plugins "essential" } "2" => { get_preset_plugins $plugins "development" } "3" => { $plugins } _ => { [] } } } # Install plugins to ~/.local/bin/ def install_plugins [selected: list, source_dir: string] { let install_dir = $"($env.HOME)/.local/bin" # Ensure directory exists if not ($install_dir | path exists) { log_info $"Creating directory: ($install_dir)" mkdir $install_dir } for plugin in $selected { log_info $"Installing: ($plugin.name)" try { let source_path = $"($source_dir)/($plugin.name)" let target_path = $"($install_dir)/($plugin.name)" if not ($source_path | path exists) { log_error $" āœ— Source not found: ($source_path)" continue } cp $source_path $target_path chmod +x $target_path # Fix macOS code signing issues fix_macos_binary $target_path log_success $" āœ“ Installed to ($target_path)" } catch {|err| log_error $" āœ— Failed: ($err.msg)" } } } # Fix macOS code signing (remove quarantine, ad-hoc sign) def fix_macos_binary [binary_path: string] { let os_type = $nu.os-info.name if $os_type == "macos" { # Remove quarantine attribute try { ^xattr -d com.apple.quarantine $binary_path out+err>| null } catch { # Silently ignore if attribute doesn't exist } # Ad-hoc sign the binary try { ^codesign -s - $binary_path out+err>| null } catch { # Silently ignore if codesign fails } } } # Register plugins with Nushell def register_plugins [selected: list] { let install_dir = $"($env.HOME)/.local/bin" let nu_cmd = $"($install_dir)/nu" # Fix code signing on nu binary if it exists locally if ($nu_cmd | path exists) { fix_macos_binary $nu_cmd } for plugin in $selected { let basename = $plugin.name | str replace "^nu_plugin_" "" let plugin_path = $"($install_dir)/($plugin.name)" log_info $"Registering: ($plugin.name)" try { # Remove old registration if exists try { if ($nu_cmd | path exists) { ^$nu_cmd -c $"plugin rm ($basename)" out+err>| null } else { nu -c $"plugin rm ($basename)" out+err>| null } } catch {} # Register new - use local nu if available, otherwise system nu if ($nu_cmd | path exists) { ^$nu_cmd -c $"plugin add ($plugin_path)" } else { nu -c $"plugin add ($plugin_path)" } log_success $" āœ“ Registered" } catch {|err| log_error $" āœ— Failed: ($err.msg)" } } } # Logging def log_info [msg: string] { print $"ā„¹ļø ($msg)" } def log_success [msg: string] { print $"āœ… ($msg)" } def log_error [msg: string] { print $"āŒ ($msg)" } def log_warn [msg: string] { print $"āš ļø ($msg)" } main