This guide provides comprehensive instructions for generating and updating Zimagi command specifications and implementations within Zimagi modules. Zimagi uses a meta-programming architecture where command behavior is defined through YAML specifications that generate dynamic parent classes, which are then extended by Python implementations.
Important: All new commands, command mixins, and base commands must be added within Zimagi modules (separate Git projects), not in the Zimagi core project. The core project provides the framework, while modules extend it with custom functionality.
- Command Specifications (lib/modules/[module_name]/spec/): YAML files defining command structure, parameters, and behavior
- Command Implementations (lib/modules/[module_name]/commands/): Python classes containing execution logic
- Command Mixin Implementations (lib/modules/[module_name]/commands/mixins/): Python classes containing reusable execution logic
- Base Command System (app/systems/commands/): Core framework providing meta-programming capabilities
- API Integration (app/systems/api/command/): Automatic API endpoint generation
All commands exist in a hierarchical command tree that can be completely defined by the user. The top-level commands are directly accessible as top-level paths in the command API or CLI. For example:
# Top level command
zimagi mycommand
# Nested command
zimagi myservice mycommandThe command tree structure is defined through the specification hierarchy which must match precisely the directory hierarchy within the commands directory and determines both CLI accessibility and API endpoint paths.
- Define specification in lib/modules/[module_name]/spec/
- System generates parent command class that inherits from base command class via meta-programming
- Create implementation class extending generated parent class in lib/modules/[module_name]/commands/
- Add exec() method with business logic
- System automatically creates API endpoints based on command tree position
Command specifications are defined in YAML format with the following hierarchy:
command:
[command_name]:
[subcommand_name]:
base: [base_command_name]
[properties]: [values]
parameters:
[param_name]:
parser: [flag|variable|variables|fields]
[properties]: [values]
parse:
- [parameter_names]- base: Specifies the base command class to inherit from
- priority: Execution priority (lower numbers occur first in help lists)
- background: Whether command runs in background (async in an automatically managed Celery worker)
- confirm: Whether confirmation is required before execution
- api_enabled: Whether API endpoint is generated
- mcp_enabled: Whether command is available via MCP protocol
- worker_type: Type of worker to execute command
- groups_allowed: User groups permitted to execute command
- flag: Boolean toggle parameters
- variable: Single value parameters
- variables: Multiple value parameters (comma-separated on CLI or list in JSON)
- fields: Key-value pair parameters (field=value on CLI or dictionary in JSON)
- parser: Type of parameter (flag, variable, variables, fields)
- type: Item data type (str, int, float, bool)
- optional: Flag for optional parameters (false for required, true for optional argument name, --[field name] for option name)
- default: Default value
- help: Help text description
- value_label: Label for value in help text
- tags: Categorization tags (add mcp tag if this parameter should be included in MCP API if mcp_enabled on command specification)
- system: Whether parameter is system-only
- choices: List of valid value options
Examine app/spec/base/command.yml to understand available base commands:
- platform: Platform management commands
- host: Host management commands
- user: User management commands
- config: Configuration commands
- module: Module management commands
- db: Database commands
- cache: Cache management commands
- log: Log management commands
- agent: Service agent commands
Zimagi modules can also add new base commands that have different configuration variations.
- Action Commands: - Direct implementation with custom parameters - Use mixins for additional functionality - Define exec and optional parse methods
- Agent Commands: - Background controller managed service commands - Use process-based architecture that can run multiple processes concurrently - Implement specific process methods - Usually implement the listen and send methods from the base agent command library
Note: Resource management commands (list, get, save, remove, clear) are automatically generated during the Zimagi build process based on data models. See build-spec.rst for details on how these are created.
Determine what the command should accomplish: - Action execution (one-time operations) - Background processing (continuous operations) - Data processing (import/export/transform)
Choose appropriate base from app/spec/base/command.yml or design your own: - For actions: use platform, user, config, etc. - For background services: use agent
Based on command purpose, define required parameters: - Use existing mixins for common functionality - Design your own command mixins for new reusable parameters and optional method implementations - Define custom parameters for unique requirements - Apply appropriate parser types - Set sensible defaults
Set command properties based on requirements: - priority: Lower for frequently used commands - background: True for long-running operations - confirm: True for destructive operations - api_enabled: False for CLI-only commands - groups_allowed: Restrict to appropriate user groups
Base commands provide reusable configuration templates that can be inherited by multiple commands within a module or across modules. They establish common behavior, parameters, and access controls.
Base commands are defined in lib/modules/[module_name]/spec/base/command.yml or in module-specific base command files:
command_base:
[base_name]:
groups_allowed: [admin]
background: false
priority: 10
mixins: [mixin1, mixin2]
parameters:
common_param:
parser: variable
type: str
optional: '--common'
help: 'Common parameter for all inheriting commands'
tags: [common]Commands can inherit from base commands using the base property:
command:
my_command:
base: [base_name]
# Override or extend base properties
groups_allowed: [admin, user]
# Add specific parameters
parameters:
specific_param:
parser: variable
type: str
optional: false
help: 'Specific parameter for this command'Command mixins provide reusable parameter combinations and method implementations that can be included in multiple commands. They are defined in lib/modules/[module_name]/spec/mixins/command.yml or in module-specific mixin files.
Mixins are defined with a class name and can include meta properties, parameters, and method implementations:
command_mixins:
[mixin_name]:
class: [MixinClassName]
parameters:
[param_name]:
parser: [flag|variable|variables|fields]
[properties]: [values]Commands can include multiple mixins using the mixins property:
command:
my_command:
base: platform
mixins: [mixin1, mixin2]
parameters:
# Command-specific parametersMixin implementations are created in lib/modules/[module_name]/commands/mixins/ and can provide both parameter parsing and method implementations that are automatically available to inheriting commands.
Create Python file in lib/modules/[module_name]/commands/ with class structure:
The command specification YAML path should directly match the directory path in the command tree for the command.
from systems.commands.index import Command
class [CommandName](Command("[spec_path]")):
def exec(self):
# Implementation logic here
passIn the exec() method, implement the command's functionality: - Access parameters via generated property accessors - Use mixin methods for common operations - Handle errors appropriately - Provide user feedback via message methods
Utilize built-in functionality: - self.[parameter_name] for parameter access - self.success(), self.warning(), self.error(), self.info(), etc. for user feedback - self.run_list() for parallel processing - self.run_exclusive() for mutex operations - Mixin methods for specialized functionality
All parameters defined in specifications are automatically available as properties:
# For variable parameter
value = self.parameter_name
# For variables parameter (returns list)
values = self.parameter_names
# For fields parameter (returns dict)
fields = self.parameter_fields- Simple Action Execution:
def exec(self):
# Process parameter
name = self.resource_name
# Perform action
result = self.do_something(name)
# Provide feedback
self.success(f"Successfully processed {name}: {result}")- Background Processing:
def exec(self):
# Listen for events
for package in self.listen("channel:name"):
# Process event
self.process_event(package.message)
# Send response
self.send("response:channel", result)API endpoints are automatically generated based on: - Command path in specification hierarchy - api_enabled property - Parameter definitions - User access controls
The endpoint will be available at: <command.api.host>/[command]/[subcommand]/...
The command API endpoint accepts a POST parameter / value dictionary and streams newline separated JSON messages that are generated by the message commands like self.info(), self.success(), self.warning(), self.data(), and self.table, and self.error().
Zimagi provides several message functions that allow commands to communicate status, results, and errors to users. These functions behave differently depending on the execution context (CLI, Command API, MCP API, or background workers), but the core interface remains consistent.
The following message functions are available in all command implementations:
- self.info(message, name=None, prefix=None, log=True, system=False) - General informational messages - Used for standard command output
- self.data(label, value, name=None, prefix=None, silent=False, system=False, log=True) - Structured data output - Useful for programmatic consumption - Provide a name parameter to help with access on the client with the same name adding to a list
- self.table(data, name=None, prefix=None, silent=False, system=False, row_labels=False, log=True) - Tabular data presentation - Good for lists and structured information - Provide a name parameter to help with access on the client
- self.warning(message, name=None, prefix=None, system=False, log=True) - Non-fatal warning messages - Indicates potential issues
- self.notice(message, name=None, prefix=None, system=False, log=True) - Important informational messages - More prominent than standard info
- self.success(message, name=None, prefix=None, system=False, log=True) - Success confirmation messages - Indicates successful completion of operations
- self.error(message, name=None, prefix=None, system=False, terminate=True, traceback=None, error_cls=CommandError, silent=False) - Error messages that can terminate execution - Critical for error handling - Raises a error_cls exception if terminate option is true
All message functions accept these common parameters:
- message/label/value/data: The main content to display
- name: Identifier for the message (used in API contexts)
- prefix: Prefix text for the message
- system: Whether this is a system message (may be hidden from regular users)
- log: Whether to log the message
- silent: Whether to suppress display (still logged and sent via API)
In CLI mode, messages are displayed directly to the terminal with appropriate coloring:
- info(): Standard output with default coloring
- data(): Label in key color, value in value color
- table(): Formatted table output with header coloring
- warning(): Yellow warning text
- notice(): Notice text with special formatting
- success(): Green success text
- error(): Red error text with optional traceback
In Command API mode, messages are streamed as newline-separated JSON objects:
- Each message function generates a specific message type
- Messages include metadata like type, name, and content
- Data is encrypted based on user permissions
- Messages are streamed in real-time as they're generated
In MCP API mode, messages are formatted for LLM consumption:
- Simplified output format optimized for language models
- Structured data is formatted as code blocks
- Messages are categorized by type for appropriate handling
- Error messages include tracebacks when debugging is enabled
In background workers, messages are:
- Logged to the system log
- Stored in the command execution log
- Available for later retrieval via log commands
- Processed for notifications if configured
- Simple Information Output:
def exec(self):
# Basic info message
self.info("Starting processing...")
# Success message
self.success("Processing completed successfully")- Structured Data Output:
def exec(self):
results = {"processed": 100, "errors": 0}
# Output structured data
self.data("Processing Results", results, "results")
# Output as table
self.table([
["Metric", "Value"],
["Processed", results["processed"]],
["Errors", results["errors"]]
], "summary")- Warning and Error Handling:
def exec(self):
try:
result = self.risky_operation()
except Exception as e:
# Warning that doesn't stop execution
self.warning(f"Operation had issues: {e}")
# Continue with fallback
try:
critical_result = self.critical_operation()
except Exception as e:
# Error that terminates execution
self.error(f"Critical operation failed: {e}")- Cross-Platform Compatible Output:
def exec(self):
# This will work consistently across all execution contexts
self.info("Processing data...")
data = self.get_data()
self.data("Retrieved Data", data, "retrieved_data")
# Table works in all contexts but displays differently
if data:
self.table([
["ID", "Name", "Status"],
*[[item.id, item.name, item.status] for item in data]
], "data_summary")
self.success("Data processing complete")Use appropriate message types: - info() for general progress - data() for structured output - table() for tabular information - warning() for non-fatal issues - success() for completion confirmation - error() for problems that need attention
Provide meaningful names for API consumption: .. code-block:: python
self.data("User Profile", profile_data, "user_profile") self.table(user_list, "user_list", row_labels=True)
Consider system vs. user messages: .. code-block:: python
self.info("Internal processing step", system=True) # Hidden from regular users and MCP commands self.info("Processing complete") # Visible to all users
Use silent parameter for programmatic consumption: .. code-block:: python
self.data("Debug Info", debug_data, silent=True) # Logged but not displayed
Handle errors appropriately: .. code-block:: python
- try:
self.process_data()
- except ValueError as e:
self.error(f"Invalid data format: {e}", terminate=False) # Continue execution
- except Exception as e:
self.error(f"Processing failed: {e}") # Terminate execution
Leverage existing mixins for common functionality: - config: Configuration management - db: Database operations - log: Logging capabilities - notification: User notifications - schedule: Scheduled execution
Create new mixins in lib/modules/[module_name]/spec/mixins/command.yml when needed: - Add parameter definitions - Implement common methods in lib/modules/[module_name]/commands/mixins/
Use appropriate error handling patterns: - self.error() for user-facing errors - Proper exception handling for system errors - Validation before destructive operations
- Test parameter parsing
- Test business logic
- Test error conditions
- Test API endpoint generation
- Test user access controls
Module Structure:
mymodule/
├── spec/
│ ├── base/
│ │ └── command.yml
│ ├── mixins/
│ │ └── command.yml
│ └── commands/
│ └── process.yml
└── commands/
├── mixins/
│ └── file_processor.py
│ └── message_handler.py
└── process.py
Base Command Specification (lib/modules/mymodule/spec/base/command.yml):
command_base:
data_processor:
groups_allowed: [admin, data-user]
background: true
mixins: [log]
parameters:
input_file:
parser: variable
type: str
optional: '--input'
help: 'Input file path'
value_label: PATH
tags: [data]
output_format:
parser: variable
type: str
default: json
optional: '--format'
help: 'Output format'
choices: [json, csv, xml]
tags: [data]Mixin Specification (lib/modules/[mymodule]/spec/mixins/command.yml):
command_mixins:
file_processor:
class: FileProcessorMixin
parameters:
file_path:
parser: variable
type: str
optional: false
help: 'Path to file for processing'
value_label: PATH
tags: [file]
message_handler:
class: MessageHandlerMixinCommand Specification (lib/modules/[mymodule]/spec/commands/process.yml):
command:
process:
base: data_processor
mixins: [file_processor, message_handler]
priority: 200
parameters:
process_type:
parser: variable
type: str
optional: '--type'
help: 'Type of processing to perform'
choices: [transform, validate, enrich]
tags: [process]
parse:
- input_file
- output_format
- file_path
- process_typeMixin Implementation (lib/modules/[mymodule]/commands/mixins/file_processor.py):
from systems.commands.index import CommandMixin
class FileProcessorMixin(CommandMixin("file_processor")):
def process_file_data(self, file_path):
# Common file processing logic
return f"Processed data from {file_path}"Mixin Implementation (lib/modules/[mymodule]/commands/mixins/message_handler.py):
from systems.commands.index import CommandMixin
class MessageHandlerMixin(CommandMixin("message_handler")):
def log_processing_step(self, step_name, details=None):
"""Log a processing step with optional details"""
self.info(f"Processing step: {step_name}")
if details:
self.data(f"{step_name} Details", details, f"{step_name.lower()}_details")Command Implementation (lib/modules/[mymodule]/commands/process.py):
from systems.commands.index import Command
class Process(Command("process")):
def exec(self):
# Use message mixin for consistent logging
self.log_processing_step("Initialization")
# Access parameters via auto-generated properties
input_file = self.input_file
file_path = self.file_path
process_type = self.process_type
# Provide progress updates
self.info(f"Processing file: {file_path}")
self.info(f"Process type: {process_type}")
# Use mixin methods if available
processed_data = self.process_file_data(file_path)
self.log_processing_step("File Processing", {"file": file_path, "data_size": len(processed_data)})
# Perform business logic
try:
result = self.process_file(input_file, file_path, process_type, processed_data)
# Provide success feedback with structured data
self.success(f"Successfully processed {file_path}")
# Output data in multiple formats for different consumption methods
self.data("Processing Result", result, "result")
# Provide summary table for easy scanning
self.table([
["Metric", "Value"],
["File", file_path],
["Process Type", process_type],
["Status", "Success"],
["Result Size", len(str(result))]
], "processing_summary")
except Exception as e:
# Handle errors appropriately for all contexts
self.error(f"Failed to process file: {e}")
def process_file(self, input_file, file_path, process_type, processed_data):
# Implementation logic here
return f"Processed {file_path} with {process_type}: {processed_data}"This example demonstrates: - Base command inheritance within a module - Mixin inclusion with implementation - Parameter definition and access - Error handling - Cross-context message output - Data output capabilities
- Use descriptive command and parameter names
- Provide clear help text for all parameters
- Set appropriate defaults where possible
- Use tags for categorization and filtering
- Follow existing naming conventions
- Create base commands for common patterns
- Use mixins for reusable parameter sets
- Keep exec() method focused and readable
- Extract complex logic to helper methods
- Use appropriate error handling
- Provide meaningful user feedback
- Leverage system capabilities rather than reimplementing
- Use mixin methods when available
- Use self.run_list() for parallel processing
- Use self.run_exclusive() for mutex operations
- Consider background processing for long operations
- Optimize database queries
- Cache expensive operations when appropriate
- Set appropriate groups_allowed restrictions
- Validate user input
- Check access controls for sensitive operations
- Sanitize output data
- Follow principle of least privilege
- Keep specifications and implementations in sync
- Document complex business logic
- Follow consistent coding patterns
- Test thoroughly before deployment
- Monitor command usage and performance
This specification guide provides a comprehensive framework for generating Zimagi commands within modules. By following these patterns and leveraging the system's meta-programming capabilities, you can efficiently create robust, maintainable commands with automatic API integration. Remember that resource management commands are automatically generated during the build process and should not be manually created.
All commands exist within a hierarchical command tree that determines both CLI accessibility and API endpoint paths. The top-level commands are directly accessible as top-level paths in the command API or CLI, making it easy to organize and access your custom functionality.