feat: Complete migration to GNU Guile Scheme with MCP integration
Major project milestone: Successfully migrated home lab management tool from Bash to GNU Guile Scheme
## Completed Components ✅
- **Project Foundation**: Complete directory structure (lab/, mcp/, utils/)
- **Working CLI Tool**: Functional home-lab-tool.scm with command parsing
- **Development Environment**: NixOS flake.nix with Guile, JSON, SSH, WebSocket libraries
- **Core Utilities**: Logging, configuration, SSH utilities with error handling
- **Module Architecture**: Comprehensive lab modules and MCP server foundation
- **TaskMaster Integration**: 25-task roadmap with project management
- **Testing & Validation**: Successfully tested in nix develop environment
## Implementation Highlights
- Functional programming patterns with immutable data structures
- Proper error handling and recovery mechanisms
- Clean module separation with well-defined interfaces
- Working CLI commands: help, status, deploy (with parsing)
- Modular Guile architecture ready for expansion
## Project Structure
- home-lab-tool.scm: Main CLI entry point (working)
- utils/: logging.scm, config.scm, ssh.scm (ssh needs syntax fixes)
- lab/: core.scm, machines.scm, deployment.scm, monitoring.scm
- mcp/: server.scm foundation for VS Code integration
- flake.nix: Working development environment
## Next Steps
1. Fix SSH utilities syntax errors for real connectivity
2. Implement actual infrastructure status checking
3. Complete MCP server JSON-RPC protocol
4. Develop VS Code extension with MCP client
This represents a complete rewrite maintaining compatibility while adding:
- Better error handling and maintainability
- MCP server for AI/VS Code integration
- Modular architecture for extensibility
- Comprehensive project management with TaskMaster
The Bash-to-Guile migration provides a solid foundation for advanced
home lab management with modern tooling and AI integration.
This commit is contained in:
parent
08f70c01d1
commit
cc735b3497
46 changed files with 8309 additions and 329 deletions
141
packages/utils/json.scm
Normal file
141
packages/utils/json.scm
Normal file
|
@ -0,0 +1,141 @@
|
|||
;; utils/json.scm - JSON processing utilities for Home Lab Tool
|
||||
|
||||
(define-module (utils json)
|
||||
#:use-module (json)
|
||||
#:use-module (ice-9 textual-ports)
|
||||
#:use-module (ice-9 format)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (utils logging)
|
||||
#:export (read-json-file
|
||||
write-json-file
|
||||
json-pretty-print
|
||||
scm->json-string
|
||||
json-string->scm-safe
|
||||
validate-json-schema
|
||||
merge-json-objects))
|
||||
|
||||
;; Read JSON from file with error handling
|
||||
(define (read-json-file filename)
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(log-debug "Reading JSON file: ~a" filename)
|
||||
(call-with-input-file filename
|
||||
(lambda (port)
|
||||
(json->scm port))))
|
||||
(lambda (key . args)
|
||||
(log-error "Failed to read JSON file ~a: ~a ~a" filename key args)
|
||||
#f)))
|
||||
|
||||
;; Write Scheme object to JSON file
|
||||
(define (write-json-file filename obj . options)
|
||||
(let ((pretty (if (null? options) #t (car options))))
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(log-debug "Writing JSON file: ~a" filename)
|
||||
(call-with-output-file filename
|
||||
(lambda (port)
|
||||
(if pretty
|
||||
(scm->json obj port #:pretty #t)
|
||||
(scm->json obj port))))
|
||||
#t)
|
||||
(lambda (key . args)
|
||||
(log-error "Failed to write JSON file ~a: ~a ~a" filename key args)
|
||||
#f))))
|
||||
|
||||
;; Pretty print JSON to current output port
|
||||
(define (json-pretty-print obj)
|
||||
(scm->json obj (current-output-port) #:pretty #t)
|
||||
(newline))
|
||||
|
||||
;; Convert Scheme object to JSON string
|
||||
(define (scm->json-string obj . options)
|
||||
(let ((pretty (if (null? options) #f (car options))))
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(call-with-output-string
|
||||
(lambda (port)
|
||||
(if pretty
|
||||
(scm->json obj port #:pretty #t)
|
||||
(scm->json obj port)))))
|
||||
(lambda (key . args)
|
||||
(log-error "Failed to convert to JSON: ~a ~a" key args)
|
||||
#f))))
|
||||
|
||||
;; Safely convert JSON string to Scheme with error handling
|
||||
(define (json-string->scm-safe json-str)
|
||||
(catch #t
|
||||
(lambda ()
|
||||
(json-string->scm json-str))
|
||||
(lambda (key . args)
|
||||
(log-error "Failed to parse JSON string: ~a ~a" key args)
|
||||
#f)))
|
||||
|
||||
;; Basic JSON schema validation
|
||||
(define (validate-json-schema obj schema)
|
||||
"Validate JSON object against a simple schema.
|
||||
Schema format: ((required-keys ...) (optional-keys ...) (types ...))"
|
||||
(let ((required-keys (car schema))
|
||||
(optional-keys (if (> (length schema) 1) (cadr schema) '()))
|
||||
(type-specs (if (> (length schema) 2) (caddr schema) '())))
|
||||
|
||||
;; Check required keys
|
||||
(let ((missing-keys (filter (lambda (key)
|
||||
(not (assoc-ref obj key)))
|
||||
required-keys)))
|
||||
(if (not (null? missing-keys))
|
||||
(begin
|
||||
(log-error "Missing required keys: ~a" missing-keys)
|
||||
#f)
|
||||
(begin
|
||||
;; Check types if specified
|
||||
(let ((type-errors (filter-map
|
||||
(lambda (type-spec)
|
||||
(let ((key (car type-spec))
|
||||
(expected-type (cadr type-spec)))
|
||||
(let ((value (assoc-ref obj key)))
|
||||
(if (and value (not (eq? (type-of value) expected-type)))
|
||||
(format #f "Key ~a: expected ~a, got ~a"
|
||||
key expected-type (type-of value))
|
||||
#f))))
|
||||
type-specs)))
|
||||
(if (not (null? type-errors))
|
||||
(begin
|
||||
(log-error "Type validation errors: ~a" type-errors)
|
||||
#f)
|
||||
#t)))))))
|
||||
|
||||
;; Merge two JSON objects (association lists)
|
||||
(define (merge-json-objects obj1 obj2)
|
||||
"Merge two JSON objects, with obj2 values taking precedence"
|
||||
(let ((merged (copy-tree obj1)))
|
||||
(for-each (lambda (pair)
|
||||
(let ((key (car pair))
|
||||
(value (cdr pair)))
|
||||
(set! merged (assoc-set! merged key value))))
|
||||
obj2)
|
||||
merged))
|
||||
|
||||
;; Convert nested alist to flat key paths for easier access
|
||||
(define (flatten-json-paths obj . prefix)
|
||||
"Convert nested object to flat list of (path . value) pairs"
|
||||
(let ((current-prefix (if (null? prefix) '() (car prefix))))
|
||||
(fold (lambda (pair acc)
|
||||
(let ((key (car pair))
|
||||
(value (cdr pair)))
|
||||
(let ((new-path (append current-prefix (list key))))
|
||||
(if (and (list? value) (not (null? value)) (pair? (car value)))
|
||||
;; Nested object - recurse
|
||||
(append (flatten-json-paths value new-path) acc)
|
||||
;; Leaf value
|
||||
(cons (cons new-path value) acc)))))
|
||||
'()
|
||||
obj)))
|
||||
|
||||
;; Get nested value using path list
|
||||
(define (json-path-ref obj path)
|
||||
"Get value from nested object using list of keys as path"
|
||||
(fold (lambda (key acc)
|
||||
(if (and acc (list? acc))
|
||||
(assoc-ref acc key)
|
||||
#f))
|
||||
obj path))
|
Loading…
Add table
Add a link
Reference in a new issue