# Workspace Management CLI Commands # Commands for switching between workspaces and managing workspace registry # Selective imports (ADR-025 Phase 3 Layer 2). # Pre-existing name collision: get-workspace-path and list-workspaces are # exported by BOTH user/config.nu and workspace/notation.nu. Original star # imports resolved via last-wins (notation.nu line was after user/config.nu). # Keep notation.nu as the owner for the 2 collision symbols. use platform/user/config.nu [ get-active-workspace get-active-workspace-details get-user-preference load-user-config register-workspace remove-workspace set-active-workspace set-user-preference set-workspace-default-infra validate-workspace-exists ] use primitives/io/hints.nu [show-next-step] use platform/activation.nu [activate-workspace-platform] use platform/workspace/notation.nu [ get-workspace-path list-workspaces parse-workspace-infra-notation ] use platform/clients/fallback.nu [tool-list] use primitives/io/fmt_output.nu [fmt-output] # Activate a workspace (set as current) export def "workspace activate" [ spec: string # Name of workspace or workspace:infra notation --quiet (-q) # Suppress output messages ] { let parsed = (parse-workspace-infra-notation $spec) let workspace_name = $parsed.workspace if not (validate-workspace-exists $workspace_name) { print $"(ansi red)✗(ansi reset) Workspace '($workspace_name)' not found in registry" print $"(ansi yellow)💡(ansi reset) Available workspaces:" workspace list print "" print $"(ansi yellow)💡(ansi reset) Register it first with: provisioning workspace register ($workspace_name) " return } let workspace_path = (get-workspace-path $workspace_name) if not ($workspace_path | path exists) { print $"(ansi red)✗(ansi reset) Workspace directory not found: ($workspace_path)" print $"(ansi yellow)💡(ansi reset) The workspace may have been moved or deleted" return } let config_path = ($workspace_path | path join "config") if not ($config_path | path exists) { print $"(ansi red)✗(ansi reset) Workspace is not migrated to new config system" print $"(ansi yellow)💡(ansi reset) Missing: ($config_path)" print $"(ansi yellow)💡(ansi reset) Run migration: provisioning workspace migrate ($workspace_name)" return } let provisioning_nickel = ($config_path | path join "provisioning.ncl") let provisioning_yaml = ($config_path | path join "provisioning.yaml") if not (($provisioning_nickel | path exists) or ($provisioning_yaml | path exists)) { print $"(ansi red)✗(ansi reset) Missing workspace configuration" print $"(ansi yellow)💡(ansi reset) Missing: ($provisioning_nickel) or ($provisioning_yaml)" print $"(ansi yellow)💡(ansi reset) Run migration: provisioning workspace migrate ($workspace_name)" return } set-active-workspace $workspace_name $workspace_path if ($parsed.infra | is-not-empty) { let infra_path = ([$workspace_path "infra" $parsed.infra] | path join) let settings_file = ([$infra_path "settings.ncl"] | path join) if not ($settings_file | path exists) { print $"(ansi red)✗(ansi reset) Infrastructure '($parsed.infra)' not found in workspace '($workspace_name)'" print $"(ansi yellow)💡(ansi reset) Missing: ($settings_file)" return } set-workspace-default-infra $workspace_name $parsed.infra if not $quiet { print $"(ansi green)✓(ansi reset) Default infrastructure set to '($parsed.infra)'" } } let platform_activated = (activate-workspace-platform $workspace_name --auto-start --wait --skip-optional) if not $platform_activated { print $"(ansi yellow)⚠️(ansi reset) Platform activation warning: services not fully available" print $"(ansi yellow)💡(ansi reset) You can still use the workspace, but some services may not be available" } if not $quiet { show-next-step "workspace_activate" {name: $workspace_name} } } # Switch to another workspace (alias for activate) export def "workspace switch" [ workspace_name: string # Name of workspace to switch to --quiet (-q) # Suppress output messages ] { workspace activate $workspace_name --quiet=$quiet } # List all known workspaces export def "workspace list" [ --fmt (-f): string = "text" # Output format: text, json, yaml, toml, md --clip (-c) # Copy output to clipboard --notitles # Suppress section title and spacing (text mode only) ] { # Tier-1 daemon / Tier-2 provisioning-tool / Tier-3 Nushell legacy let workspaces = (tool-list "workspace_list" {} { list-workspaces }) if ($workspaces | is-empty) { if not $notitles { print $"(ansi yellow)No workspaces registered(ansi reset)" print "" print $"(ansi cyan)💡(ansi reset) Register a workspace with:" print $" provisioning workspace register " } return } if $fmt != "text" { let rendered = (fmt-output $workspaces $fmt $clip) print $rendered return } # text (human) rendering if not $notitles { print "" print $"(ansi green_bold)Registered Workspaces:(ansi reset)" print "" } let active = (get-active-workspace) let lines = ($workspaces | each { |ws| let is_active = ($ws.name == $active) let active_marker = if $is_active { $"(ansi green)●(ansi reset)" } else { " " } let name_display = if $is_active { $"(ansi green_bold)($ws.name)(ansi reset)" } else { $ws.name } $" ($active_marker) ($name_display)\n Path: ($ws.path?|default "")\n Last used: ($ws.last_used?|default "")" }) let output = ($lines | str join "\n") if $clip { # strip ANSI for clipboard let plain = ($lines | each {|l| $l | ansi strip} | str join "\n") fmt-output $plain "text" true | ignore } print $output } # Show currently active workspace export def "workspace active" [] { let active_workspace = (get-active-workspace) if ($active_workspace == null) { print $"(ansi yellow)No active workspace(ansi reset)" print "" print $"(ansi cyan)💡(ansi reset) Activate a workspace with:" print $" provisioning workspace activate " return } let workspace_details = (get-active-workspace-details) print "" print $"(ansi green_bold)Active Workspace:(ansi reset)" print $" Name: (ansi green)($workspace_details.name)(ansi reset)" print $" Path: ($workspace_details.path)" print $" Last used: ($workspace_details.last_used)" print "" } # Register a workspace in the central registry export def "workspace register" [ workspace_name: string # Name of workspace workspace_path: string # Path to workspace directory --activate (-a) # Activate after registering ] { let expanded_path = ($workspace_path | path expand) if not ($expanded_path | path exists) { print $"(ansi red)✗(ansi reset) Directory not found: ($expanded_path)" return } if ($expanded_path | path type) != "dir" { print $"(ansi red)✗(ansi reset) Not a directory: ($expanded_path)" return } register-workspace $workspace_name $expanded_path if $activate { print "" workspace activate $workspace_name } } # Remove workspace from registry (does not delete files) export def "workspace remove" [ workspace_name: string # Name of workspace to remove --force (-f) # Skip confirmation prompt ] { if not (validate-workspace-exists $workspace_name) { print $"(ansi red)✗(ansi reset) Workspace '($workspace_name)' not found in registry" return } if not $force { print $"(ansi yellow)⚠(ansi reset) This will remove '($workspace_name)' from the registry" print $"(ansi cyan)ℹ(ansi reset) The workspace files will NOT be deleted" print "" let response = (input "Continue? (y/N): ") if $response != "y" and $response != "Y" { print $"(ansi yellow)Cancelled(ansi reset)" return } } remove-workspace $workspace_name } # Show workspace preferences export def "workspace preferences" [ --format (-f): string = "table" # Output format: table, json, yaml ] { let config = (load-user-config) match $format { "json" => { $config.preferences | to json } "yaml" => { $config.preferences | to yaml } _ => { print "" print $"(ansi green_bold)User Preferences:(ansi reset)" print "" $config.preferences | transpose key value | each { |pref| print $" ($pref.key): (ansi cyan)($pref.value)(ansi reset)" } print "" } } } # Set workspace preference export def "workspace set-preference" [ key: string # Preference key value: string # Preference value ] { set-user-preference $key $value } # Get workspace preference export def "workspace get-preference" [ key: string # Preference key ] { let value = (get-user-preference $key) if ($value == null) { print $"(ansi yellow)Preference '($key)' not found(ansi reset)" } else { print $value } } # Show workspace version information export def "workspace version" [ workspace_name?: string # Workspace name (default: active workspace) --format (-f): string = "table" # Output format: table, json, yaml ] { use domain/workspace/version.nu * let ws_name = if ($workspace_name | is-not-empty) { $workspace_name } else { let active = (get-active-workspace) if ($active == null or ($active | is-empty)) { print $"(ansi red)No active workspace(ansi reset)" print $"(ansi yellow)Specify workspace name or activate a workspace first(ansi reset)" return } $active } let ws_path = (get-workspace-path $ws_name) if ($ws_path == null or not ($ws_path | path exists)) { print $"(ansi red)Workspace not found: ($ws_name)(ansi reset)" return } let summary = (get-version-summary $ws_path) match $format { "json" => { $summary | to json } "yaml" => { $summary | to yaml } _ => { print "" print $"(ansi green_bold)Workspace Version Information(ansi reset)" print "" print $"(ansi cyan)System:(ansi reset)" print $" Version: ($summary.system.version)" print "" print $"(ansi cyan)Workspace:(ansi reset)" print $" Name: ($summary.workspace.name)" print $" Path: ($summary.workspace.path)" print $" Version: ($summary.workspace.version)" print $" Schema Version: ($summary.workspace.schema_version)" print $" Format Version: ($summary.workspace.format_version)" print $" Created: ($summary.workspace.created)" print $" Last Updated: ($summary.workspace.last_updated)" print "" print $"(ansi cyan)Compatibility:(ansi reset)" print $" Compatible: ($summary.compatibility.compatible)" print $" Reason: ($summary.compatibility.reason)" print $" Message: ($summary.compatibility.message)" if ($summary.compatibility.requires_migration? | default false) { print $" Requires Migration: (ansi yellow)Yes(ansi reset)" } print "" print $"(ansi cyan)Migrations:(ansi reset)" print $" Total: ($summary.migrations.count)" if ($summary.migrations.last_migration != null) { let last = $summary.migrations.last_migration print $" Last Migration:" print $" From: ($last.from_version) → To: ($last.to_version)" print $" Type: ($last.migration_type)" print $" Time: ($last.timestamp)" print $" Success: ($last.success)" } print "" } } } # Migrate workspace to newer version export def "workspace migrate" [ workspace_name?: string # Workspace name (default: active workspace) --skip-backup (-s) --force (-f) --target-version: string ] { use domain/workspace/migration.nu * let ws_name = if ($workspace_name | is-not-empty) { $workspace_name } else { let active = (get-active-workspace) if ($active == null or ($active | is-empty)) { print $"(ansi red)No active workspace(ansi reset)" print $"(ansi yellow)Specify workspace name or activate a workspace first(ansi reset)" return } $active } let result = (migrate-workspace $ws_name --skip-backup=$skip_backup --force=$force --target-version=($target_version | default "")) if not $result.success { if ($result.cancelled? | default false) { return } print $"(ansi red)Migration failed(ansi reset)" if ($result.error | is-not-empty) { print $"Error: ($result.error)" } exit 1 } if ($result.no_migration_needed? | default false) { print $"(ansi green)✓(ansi reset) ($result.message)" return } print $"(ansi green)Migration completed successfully(ansi reset)" } # Check workspace compatibility export def "workspace check-compatibility" [ workspace_name?: string # Workspace name (default: active workspace) ] { use domain/workspace/version.nu * let ws_name = if ($workspace_name | is-not-empty) { $workspace_name } else { let active = (get-active-workspace) if ($active == null or ($active | is-empty)) { print $"(ansi red)No active workspace(ansi reset)" print $"(ansi yellow)Specify workspace name or activate a workspace first(ansi reset)" return } $active } let ws_path = (get-workspace-path $ws_name) if ($ws_path == null or not ($ws_path | path exists)) { print $"(ansi red)Workspace not found: ($ws_name)(ansi reset)" return } let compatibility = (check-workspace-compatibility $ws_path) print "" print $"(ansi green_bold)Workspace Compatibility Check(ansi reset)" print "" print $"(ansi cyan)Workspace:(ansi reset) ($ws_name)" print $"(ansi cyan)Path:(ansi reset) ($ws_path)" print "" let status_color = if $compatibility.compatible { "green" } else { "red" } let status_icon = if $compatibility.compatible { "✓" } else { "✗" } print $"(ansi ($status_color))($status_icon) ($compatibility.message)(ansi reset)" print "" if not $compatibility.compatible { print $"(ansi yellow)Reason:(ansi reset) ($compatibility.reason)" if ($compatibility.requires_migration? | default false) { print "" print $"(ansi cyan)To fix this, run:(ansi reset)" print $" provisioning workspace migrate ($ws_name)" } else if ($compatibility.requires_upgrade? | default false) { print "" print $"(ansi cyan)System or workspace needs upgrade(ansi reset)" } print "" } } # List workspace backups export def "workspace list-backups" [ workspace_name?: string # Workspace name (default: active workspace) ] { use domain/workspace/migration.nu * let ws_name = if ($workspace_name | is-not-empty) { $workspace_name } else { let active = (get-active-workspace) if ($active == null or ($active | is-empty)) { print $"(ansi red)No active workspace(ansi reset)" print $"(ansi yellow)Specify workspace name or activate a workspace first(ansi reset)" return } $active } let backups = (list-workspace-backups $ws_name) if ($backups | is-empty) { print $"(ansi yellow)No backups found for workspace '($ws_name)'(ansi reset)" return } print "" print $"(ansi green_bold)Workspace Backups for ($ws_name)(ansi reset)" print "" $backups | select name created reason size | table } # Restore workspace from backup export def "workspace restore-backup" [ backup_path: string # Path to backup directory --force (-f) ] { use domain/workspace/migration.nu * let result = (restore-workspace-from-backup $backup_path --force=$force) if not $result.success { if ($result.cancelled? | default false) { return } print $"(ansi red)Restore failed(ansi reset)" if ($result.error | is-not-empty) { print $"Error: ($result.error)" } exit 1 } }