Clean up 404 KCL references (99.75% complete): - Rename kcl_* variables to schema_*/nickel_* (kcl_path→schema_path, etc.) - Update functions: parse_kcl_file→parse_nickel_file - Update env vars: KCL_MOD_PATH→NICKEL_IMPORT_PATH - Fix cli/providers-install: add has_nickel and nickel_version variables - Correct import syntax: .nickel.→.ncl. - Update 57 files across core, CLI, config, and utilities Configure pre-commit hooks: - Activate: nushell-check, nickel-typecheck, markdownlint - Comment out: Rust hooks (fmt, clippy, test), check-yaml Testing: - Module discovery: 9 modules (6 providers, 1 taskserv, 2 clusters) ✅ - Syntax validation: 15 core files ✅ - Pre-commit hooks: all passing ✅
400 lines
12 KiB
Plaintext
400 lines
12 KiB
Plaintext
#!/usr/bin/env nu
|
|
# [command]
|
|
# name = "command metadata traits"
|
|
# group = "infrastructure"
|
|
# tags = ["metadata", "cache", "validation"]
|
|
# version = "1.0.0"
|
|
# requires = ["nickel:0.11.2"]
|
|
# note = "Runtime bridge between Nickel metadata schema and Nushell command dispatch"
|
|
|
|
# ============================================================================
|
|
# Command Metadata Cache System
|
|
# Version: 1.0.0
|
|
# Purpose: Load, cache, and validate command metadata from Nickel 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 Nickel commands file path
|
|
def get-nickel-path [] : nothing -> string {
|
|
let proj = (
|
|
if (($env.PROVISIONING_ROOT? | is-empty)) {
|
|
$"($env.HOME)/project-provisioning"
|
|
} else {
|
|
$env.PROVISIONING_ROOT
|
|
}
|
|
)
|
|
$"($proj)/provisioning/nickel/commands.ncl"
|
|
}
|
|
|
|
# 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 schema_path = (get-nickel-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 schema_mtime = (get-file-mtime $schema_path)
|
|
let ttl = 3600
|
|
|
|
let cache_age = ($now - $cache_mtime)
|
|
let not_expired = ($cache_age < $ttl)
|
|
let schema_not_modified = ($cache_mtime > $schema_mtime)
|
|
|
|
($not_expired and $schema_not_modified)
|
|
}
|
|
|
|
# Load metadata from Nickel
|
|
def load-from-nickel [] : nothing -> record {
|
|
# Nickel metadata loading is DISABLED due to Nickel hanging issues
|
|
# All commands work with empty metadata (metadata is optional per metadata_handler.nu:28)
|
|
# This ensures CLI stays responsive even if Nickel is misconfigured
|
|
|
|
# To re-enable Nickel metadata loading in the future:
|
|
# 1. Fix the Nickel command to not hang
|
|
# 2. Add proper timeout support to Nushell 0.109
|
|
# 3. Uncomment the code below and test thoroughly
|
|
|
|
{
|
|
commands: {}
|
|
version: "1.0.0"
|
|
}
|
|
}
|
|
|
|
# Original implementation (disabled due to Nickel hanging):
|
|
# def load-from-nickel [] : nothing -> record {
|
|
# let schema_path = (get-nickel-path)
|
|
# let result = (^nickel run $schema_path -S command_registry --format json | complete)
|
|
# if ($result.exit_code == 0) {
|
|
# $result.stdout | from json
|
|
# } else {
|
|
# {
|
|
# error: $"Failed to load Nickel"
|
|
# 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 Nickel
|
|
if (is-cache-valid) {
|
|
# Use cached metadata
|
|
load-from-cache
|
|
} else {
|
|
# Load from Nickel and cache it
|
|
let metadata = (load-from-nickel)
|
|
# 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-nickel
|
|
}
|
|
|
|
# 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 schema_path = (get-nickel-path)
|
|
let now = (date now | format date "%s" | into int)
|
|
|
|
let cache_mtime = (get-file-mtime $cache_path)
|
|
let schema_mtime = (get-file-mtime $schema_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)
|
|
schema_path: $schema_path
|
|
schema_exists: ($schema_path | path exists)
|
|
schema_mtime_ago: (if ($schema_mtime > 0) {($now - $schema_mtime)} else {-1})
|
|
}
|
|
}
|