# Taskserv Management Commands # Purpose: Main interface for taskserv version management and operations # PAP Compliance: Config-driven, no hardcoding, graceful periods use lib_provisioning * # Main taskserv command dispatcher export def "main taskserv" [ command?: string # Subcommand: list/versions, check-updates, update, pin, unpin ...args # Additional arguments --help(-h) # Show help --notitles # Ignored flag ]: nothing -> any { if $help { show_taskserv_help return } # Show help if no command provided if ($command | is-empty) { show_taskserv_help return } match $command { "versions" | "list" => { if ($args | length) > 0 { show_taskserv_versions ($args | get 0) } else { show_taskserv_versions } } "check-updates" => { if ($args | length) > 0 { check_taskserv_updates ($args | get 0) } else { check_taskserv_updates } } "update" => { print "Feature not implemented yet. Available commands: versions" } "pin" => { print "Feature not implemented yet. Available commands: versions" } "unpin" => { print "Feature not implemented yet. Available commands: versions" } _ => { print $"Unknown taskserv command: ($command)" show_taskserv_help } } } def show_taskserv_versions [name?: string] { print "📦 Available Taskservs:" print "" # Get taskservs paths from both extensions and workspace # Try global extensions first, fall back to workspace extensions let global_extensions_path = (($env.PROVISIONING_HOME? | default $env.HOME) | path join ".provisioning-extensions") let workspace_taskservs_path = (config-get "paths.taskservs" | path expand) # Determine which extensions path to use let extensions_taskservs_path = if (($global_extensions_path | path join "taskservs" | path exists)) { $global_extensions_path | path join "taskservs" } else if (("/Users/Akasha/project-provisioning/provisioning/extensions/taskservs" | path exists)) { "/Users/Akasha/project-provisioning/provisioning/extensions/taskservs" } else { $global_extensions_path | path join "taskservs" } # Discover all taskservs from both locations mut all_taskservs = [] # Helper function to discover taskservs from a given directory def discover_from_path [base_path: string] { mut discovered = [] if not ($base_path | path exists) { return $discovered } let items = (ls $base_path | where type == "dir") for item in $items { let group_name = ($item.name | path basename) let group_path = $item.name # First check if group itself has kcl/kcl.mod (group-level taskserv) let group_kcl_path = ($group_path | path join "kcl") let group_kcl_mod = ($group_kcl_path | path join "kcl.mod") if ($group_kcl_mod | path exists) { let metadata = { name: $group_name group: $group_name } $discovered = ($discovered | append $metadata) } # Then check for taskservs in group subdirectories let subitems = (ls $group_path | where type == "dir") for subitem in $subitems { let app_name = ($subitem.name | path basename) # Skip 'kcl' and 'images' directories if (not ($app_name == "kcl") and not ($app_name == "images")) { let kcl_path = ($subitem.name | path join "kcl") let kcl_mod_path = ($kcl_path | path join "kcl.mod") # Check if this application has a kcl/kcl.mod file if ($kcl_mod_path | path exists) { let metadata = { name: $app_name group: $group_name } $discovered = ($discovered | append $metadata) } } } } return $discovered } # Discover from both locations, with extensions taking precedence $all_taskservs = ($all_taskservs | append (discover_from_path $extensions_taskservs_path)) $all_taskservs = ($all_taskservs | append (discover_from_path $workspace_taskservs_path)) # Remove duplicates (keep first occurrence, typically from extensions) mut unique_keys = [] mut final_taskservs = [] for taskserv in $all_taskservs { let key = $"($taskserv.group)/($taskserv.name)" if ($key not-in $unique_keys) { $unique_keys = ($unique_keys | append $key) $final_taskservs = ($final_taskservs | append $taskserv) } } $all_taskservs = $final_taskservs if ($all_taskservs | is-empty) { print "⚠️ No taskservs found" return } # Filter by name if provided let filtered = if ($name | is-not-empty) { $all_taskservs | where ($it.name =~ $name) or ($it.group =~ $name) } else { $all_taskservs } if ($filtered | is-empty) { print $"No taskserv found matching: ($name)" return } # Group by group name and display let grouped = ($filtered | group-by group | items { |group_name, items| { group: $group_name, apps: $items } }) for group_info in ($grouped | sort-by group) { print $" 📁 (_ansi cyan)($group_info.group)(_ansi reset)" for app in ($group_info.apps | sort-by name) { print $" • ($app.name)" } print "" } let count = ($filtered | length) let groups = ($filtered | get group | uniq | length) print $"Found ($count) taskservs" print $" - ($groups) groups" } def show_taskserv_help [] { print "Taskserv Management Commands:" print "" print " list [name] - List available taskservs" print " versions [name] - List taskserv versions (alias: list)" print " check-updates [name] - Check for available updates" print " update - Update taskserv to specific version" print " pin - Pin taskserv version (disable updates)" print " unpin - Unpin taskserv version (enable updates)" print "" print "Examples:" print " provisioning taskserv list # List all taskservs" print " provisioning t list # List all (shortcut)" print " provisioning taskserv list kubernetes # Show kubernetes info" print " provisioning taskserv check-updates # Check all for updates" print " provisioning taskserv update kubernetes 1.31.2 # Update kubernetes" print " provisioning taskserv pin kubernetes # Pin kubernetes version" } # Check for taskserv updates # Helper function to fetch latest version from GitHub API def fetch_latest_version [api_url: string, fallback: string, use_curl: bool]: nothing -> string { if $use_curl { let fetch_result = ^curl -s $api_url | complete if $fetch_result.exit_code == 0 { let response = $fetch_result.stdout | from json $response.tag_name | str replace "^v" "" } else { $fallback } } else { let response = (http get $api_url --headers [User-Agent "provisioning-version-checker"]) let response_version = ($response | get tag_name? | default null) if ($response_version | is-not-empty ) { $response_version | str replace "^v" "" } else { $fallback } } } def check_taskserv_updates [ taskserv_name?: string # Optional specific taskserv name ]: nothing -> nothing { use ../lib_provisioning/config/accessor.nu get-taskservs-path use ../lib_provisioning/config/accessor.nu get-config use ../lib_provisioning/config/loader.nu get-config-value print "🔄 Checking for taskserv updates..." print "" let taskservs_path = (get-taskservs-path) if not ($taskservs_path | path exists) { print $"⚠️ Taskservs path not found: ($taskservs_path)" return } # Get all taskservs (same logic as show_taskserv_versions) let all_k_files = (glob $"($taskservs_path)/**/*.k") let all_taskservs = ($all_k_files | each { |kcl_file| # Skip __init__.k, schema files, and other utility files if ($kcl_file | str ends-with "__init__.k") or ($kcl_file | str contains "/wrks/") or ($kcl_file | str ends-with "taskservs/version.k") { null } else { let relative_path = ($kcl_file | str replace $"($taskservs_path)/" "") let path_parts = ($relative_path | split row "/" | where { |p| $p != "" }) # Determine ID from the path structure let id = if ($path_parts | length) >= 3 { $path_parts.0 } else if ($path_parts | length) == 2 { let filename = ($kcl_file | path basename | str replace ".k" "") if $path_parts.0 == "no" { $"($path_parts.0)::($filename)" } else { $path_parts.0 } } else { ($kcl_file | path basename | str replace ".k" "") } # Read version data from version.k file let version_file = ($kcl_file | path dirname | path join "version.k") let version_info = if ($version_file | path exists) { let kcl_result = (^kcl $version_file | complete) if $kcl_result.exit_code == 0 and ($kcl_result.stdout | is-not-empty) { let result = ($kcl_result.stdout | from yaml) { current: ($result | get version? | default {} | get current? | default "") source: ($result | get version? | default {} | get source? | default "") check_latest: ($result | get version? | default {} | get check_latest? | default false) has_version: true } } else { { current: "" source: "" check_latest: false has_version: false } } } else { { current: "" source: "" check_latest: false has_version: false } } { id: $id current_version: $version_info.current source_url: $version_info.source check_latest: $version_info.check_latest has_version: $version_info.has_version } } } | where $it != null) # Filter to unique taskservs and optionally filter by name let unique_taskservs = ($all_taskservs | group-by id | items { |key, items| { id: $key current_version: ($items | where has_version | get 0? | default {} | get current_version? | default "not defined") source_url: ($items | where has_version | get 0? | default {} | get source_url? | default "") check_latest: ($items | where has_version | get 0? | default {} | get check_latest? | default false) has_version: ($items | any { |item| $item.has_version }) } } | sort-by id | if ($taskserv_name | is-not-empty) { where id == $taskserv_name } else { $in } ) if ($unique_taskservs | is-empty) { if ($taskserv_name | is-not-empty) { print $"❌ Taskserv '($taskserv_name)' not found" } else { print "❌ No taskservs found" } return } let config = get-config let use_curl = (get-config-value $config "http.use_curl" false) # Check updates for each taskserv let update_results = ($unique_taskservs | each { |taskserv| if not $taskserv.has_version { { id: $taskserv.id status: "no_version" current: "not defined" latest: "" update_available: false message: "No version defined" } } else if not $taskserv.check_latest { { id: $taskserv.id status: "pinned" current: $taskserv.current_version latest: "" update_available: false message: "Version pinned (check_latest = false)" } } else if ($taskserv.source_url | is-empty) { { id: $taskserv.id status: "no_source" current: $taskserv.current_version latest: "" update_available: false message: "No source URL for update checking" } } else { # Fetch latest version from GitHub releases API let api_url = $taskserv.source_url | str replace "github.com" "api.github.com/repos" | str replace "/releases" "/releases/latest" let latest_version = if ($taskserv.source_url | is-empty) { $taskserv.current_version } else { fetch_latest_version $api_url $taskserv.current_version $use_curl } let update_available = ($taskserv.current_version != $latest_version) let status = if $update_available { "update_available" } else { "up_to_date" } let message = if $update_available { $"Update available: ($taskserv.current_version) → ($latest_version)" } else { "Up to date" } { id: $taskserv.id status: $status current: $taskserv.current_version latest: $latest_version update_available: $update_available message: $message } } }) # Display results for result in $update_results { let icon = match $result.status { "update_available" => "🆙" "up_to_date" => "✅" "pinned" => "📌" "no_version" => "⚠️" "no_source" => "❓" _ => "❔" } print $" ($icon) ($result.id): ($result.message)" } print "" let total_count = ($update_results | length) let updates_available = ($update_results | where update_available | length) let pinned_count = ($update_results | where status == "pinned" | length) let no_version_count = ($update_results | where status == "no_version" | length) print $"📊 Summary: ($total_count) taskservs checked" print $" - ($updates_available) updates available" print $" - ($pinned_count) pinned" print $" - ($no_version_count) without version definitions" if $updates_available > 0 { print "" print "💡 To update a taskserv: provisioning taskserv update " } }