Jesús Pérez 85ce530733
feat: update provisioning core CLI, libraries, and plugins
Update core components including CLI, Nushell libraries, plugins system,
and utility scripts for the provisioning system.

CLI Updates:
- Command implementations
- CLI utilities and dispatching
- Help system improvements
- Command validation

Library Updates:
- Configuration management system
- Infrastructure validation
- Extension system improvements
- Secrets management
- Workspace operations
- Cache management system

Plugin System:
- Interactive form plugin (inquire)
- KCL integration plugin
- Performance optimization plugins
- Plugin registration system

Utilities:
- Build and distribution scripts
- Installation procedures
- Testing utilities
- Development tools

Documentation:
- Library module documentation
- Extension API guides
- Plugin usage guides
- Service management documentation

All changes are backward compatible. No breaking changes.
2025-12-11 21:57:05 +00:00

385 lines
11 KiB
Plaintext

#!/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})
}
}