#!/usr/bin/env nu # [command] # name = "command metadata traits" # group = "infrastructure" # tags = ["metadata", "cache", "validation"] # version = "1.0.0" # requires = ["kcl:0.11.2"] # note = "Runtime bridge between KCL metadata schema and Nushell command dispatch" # ============================================================================ # Command Metadata Cache System # Version: 1.0.0 # Purpose: Load, cache, and validate command metadata from KCL schema # ============================================================================ # Get cache directory def get-cache-dir [] : nothing -> string { if ($env.XDG_CACHE_HOME? | is-empty) { $"($env.HOME)/.cache/provisioning" } else { $"($env.XDG_CACHE_HOME)/provisioning" } } # Get cache file path def get-cache-path [] : nothing -> string { $"(get-cache-dir)/command_metadata.json" } # Get KCL commands file path def get-kcl-path [] : nothing -> string { let proj = ( if (($env.PROVISIONING_ROOT? | is-empty)) { $"($env.HOME)/project-provisioning" } else { $env.PROVISIONING_ROOT } ) $"($proj)/provisioning/kcl/commands.k" } # Get file modification time (macOS / Linux) def get-file-mtime [file_path: string] : nothing -> int { let result_macos = (do { ^stat -f%m $file_path } | complete) if ($result_macos.exit_code == 0) { ($result_macos.stdout | str trim | into int) } else { let result_linux = (do { ^stat -c%Y $file_path } | complete) if ($result_linux.exit_code == 0) { ($result_linux.stdout | str trim | into int) } else { 0 } } } # Check if cache is valid def is-cache-valid [] : nothing -> bool { let cache_path = (get-cache-path) let kcl_path = (get-kcl-path) if not (($cache_path | path exists)) { return false } let now = (date now | format date "%s" | into int) let cache_mtime = (get-file-mtime $cache_path) let kcl_mtime = (get-file-mtime $kcl_path) let ttl = 3600 let cache_age = ($now - $cache_mtime) let not_expired = ($cache_age < $ttl) let kcl_not_modified = ($cache_mtime > $kcl_mtime) ($not_expired and $kcl_not_modified) } # Load metadata from KCL def load-from-kcl [] : nothing -> record { let kcl_path = (get-kcl-path) let result = (^kcl run $kcl_path -S command_registry --format json | complete) if ($result.exit_code == 0) { $result.stdout | from json } else { { error: $"Failed to load KCL" commands: {} version: "1.0.0" } } } # Save metadata to cache export def cache-metadata [metadata: record] : nothing -> nothing { let dir = (get-cache-dir) let path = (get-cache-path) if not (($dir | path exists)) { ^mkdir -p $dir } # Save metadata to cache file $metadata | to json | save --force $path } # Load from cache file def load-from-cache [] : nothing -> record { let path = (get-cache-path) if not (($path | path exists)) { return {} } (open $path --raw | from json) } # Load command metadata with caching export def load-command-metadata [] : nothing -> record { # Check if cache is valid before loading from KCL if (is-cache-valid) { # Use cached metadata load-from-cache } else { # Load from KCL and cache it let metadata = (load-from-kcl) # Cache it for next time cache-metadata $metadata $metadata } } # Invalidate cache export def invalidate-cache [] : nothing -> record { let path = (get-cache-path) let _rm_result = (do { if (($path | path exists)) { ^rm $path } } | complete) load-from-kcl } # Get metadata for specific command export def get-command-metadata [name: string] : nothing -> record { let metadata = (load-command-metadata) # Check if metadata has commands field if ($metadata | type) != "record" { return {found: false, command: $name, error: "Invalid metadata"} } # Get the commands record let commands = (if ($metadata.commands | type) == "record" { $metadata.commands } else { {} }) # Get the specific command let cmd = ($commands | get -o $name) if ($cmd | is-empty) { return {found: false, command: $name} } {found: true, command: $name, metadata: $cmd} } # Check if command is interactive export def is-interactive-command [name: string] : nothing -> bool { let result = (get-command-metadata $name) if not $result.found {false} else { $result.metadata.requirements.interactive } } # Check if command requires auth export def requires-auth [name: string] : nothing -> bool { let result = (get-command-metadata $name) if not $result.found {false} else { $result.metadata.requirements.requires_auth } } # Get auth type export def get-auth-type [name: string] : nothing -> string { let result = (get-command-metadata $name) if not $result.found {"none"} else { $result.metadata.requirements.auth_type } } # Check if command requires workspace export def requires-workspace [name: string] : nothing -> bool { let result = (get-command-metadata $name) if not $result.found {true} else { $result.metadata.requirements.requires_workspace } } # Check if command has side effects export def has-side-effects [name: string] : nothing -> bool { let result = (get-command-metadata $name) if not $result.found {false} else { $result.metadata.requirements.side_effects } } # Get side effect type export def get-side-effect-type [name: string] : nothing -> string { let result = (get-command-metadata $name) if not $result.found {"none"} else { $result.metadata.requirements.side_effect_type } } # Check if confirmation required export def requires-confirmation [name: string] : nothing -> bool { let result = (get-command-metadata $name) if not $result.found {false} else { $result.metadata.requirements.requires_confirmation } } # Get min permission export def get-min-permission [name: string] : nothing -> string { let result = (get-command-metadata $name) if not $result.found {"read"} else { $result.metadata.requirements.min_permission } } # Check if slow operation export def is-slow-operation [name: string] : nothing -> bool { let result = (get-command-metadata $name) if not $result.found {false} else { $result.metadata.requirements.slow_operation } } # Get estimated time export def get-estimated-time [name: string] : nothing -> int { let result = (get-command-metadata $name) if not $result.found {1} else { $result.metadata.estimated_time } } # Get form path export def get-form-path [name: string] : nothing -> string { let result = (get-command-metadata $name) if not $result.found {""} else { if (($result.metadata.form_path? | is-empty)) {""} else { $result.metadata.form_path } } } # Validate command context export def validate-command-context [ name: string flags: record = {} ] : nothing -> record { let metadata_result = (get-command-metadata $name) if not $metadata_result.found { return {valid: false, issues: ["Command metadata not found"]} } let req = $metadata_result.metadata.requirements let issues = ( [] | if ($req.requires_workspace and (($env.PROVISIONING_WORKSPACE? | is-empty))) { append "Active workspace required" } else { . } | if ($req.requires_confirmation and not (($flags.yes? | default false) or ($flags.confirm? | default false))) { append "Confirmation required" } else { . } | if ($req.side_effects and (($req.side_effect_type | is-empty) or ($req.side_effect_type == "none"))) { append "Invalid side_effect_type" } else { . } ) { valid: (($issues | length) == 0) command: $name issues: $issues metadata: $metadata_result.metadata } } # Print validation issues export def print-validation-issues [validation: record] : nothing -> nothing { if (($validation.issues | is-empty)) { return } print $"(ansi red_bold)✗ Validation failed(ansi reset)" print "" $validation.issues | enumerate | each {|item| print $" (ansi yellow)[$($item.index)]:(ansi reset) ($item.item)" } print "" } # List all commands export def list-all-commands [] : nothing -> table { let metadata = (load-command-metadata) if (($metadata | has "error") and not (($metadata.error | is-empty))) { return [] } if not (($metadata | has "commands")) { return [] } $metadata.commands | keys | each {|cmd_name| let cmd = ($metadata.commands | get $cmd_name) let req = $cmd.requirements { name: $cmd_name domain: $cmd.domain description: $cmd.description interactive: $req.interactive requires_auth: $req.requires_auth auth_type: $req.auth_type requires_workspace: $req.requires_workspace side_effects: $req.side_effects side_effect_type: $req.side_effect_type requires_confirmation: $req.requires_confirmation min_permission: $req.min_permission slow_operation: $req.slow_operation estimated_time: $cmd.estimated_time } } } # Filter commands export def filter-commands [criteria: record] : nothing -> table { let all = (list-all-commands) $all | where {|cmd| let domain_match = if (($criteria.domain? | is-empty)) {true} else {$cmd.domain == $criteria.domain} let interactive_match = if (($criteria.interactive? | is-empty)) {true} else {$cmd.interactive == $criteria.interactive} let side_effects_match = if (($criteria.side_effects? | is-empty)) {true} else {$cmd.side_effects == $criteria.side_effects} let auth_match = if (($criteria.requires_auth? | is-empty)) {true} else {$cmd.requires_auth == $criteria.requires_auth} let slow_match = if (($criteria.slow? | is-empty)) {true} else {$cmd.slow_operation == $criteria.slow} ($domain_match and $interactive_match and $side_effects_match and $auth_match and $slow_match) } } # Cache statistics export def cache-stats [] : nothing -> record { let cache_path = (get-cache-path) let kcl_path = (get-kcl-path) let now = (date now | format date "%s" | into int) let cache_mtime = (get-file-mtime $cache_path) let kcl_mtime = (get-file-mtime $kcl_path) let cache_age = (if ($cache_mtime > 0) {($now - $cache_mtime)} else {-1}) let ttl_remain = (if ($cache_age >= 0) {(3600 - $cache_age)} else {0}) { cache_path: $cache_path cache_exists: ($cache_path | path exists) cache_age_seconds: $cache_age cache_ttl_seconds: 3600 cache_ttl_remaining: (if ($ttl_remain > 0) {$ttl_remain} else {0}) cache_valid: (is-cache-valid) kcl_path: $kcl_path kcl_exists: ($kcl_path | path exists) kcl_mtime_ago: (if ($kcl_mtime > 0) {($now - $kcl_mtime)} else {-1}) } }