Skip to content

🧩 Axium Spokes: Architecture & Contribution Guide

Axium’s Spokes are how we extend the platform — logic, not config.


Overview

Spokes are lightweight Python modules that extend Axium’s behavior by registering hooks, commands, and integrations. They never carry their own configuration files. All persistent data lives in Axium’s core config (like envs.yaml, prefixes.yaml, settings.yaml).

This design keeps Axium declarative, deterministic, and reloadable at runtime.


🔧 Philosophy: “Config lives in core, logic lives in Spokes”

Responsibility Lives in Example
Persistent configuration Core ~/.config/axium/envs.yaml, prefixes.yaml
Runtime logic & integrations Spokes Hooks, commands, event listeners
Declarative state Daemon state.json (active env, status)
Execution context Command wrapper axium run <cmd>

🧠 What a Spoke is

A Spoke is just a Python file (or package) that exports a single register() function.

def register(app, events):
    # Add CLI commands
    @app.command("aws-whoami")
    def aws_whoami():
        import boto3
        print(boto3.client("sts").get_caller_identity())

    # Subscribe to events
    def on_env_change(new_env):
        print(f"[spoke:aws] environment changed → {new_env}")
    events.on("env_change", on_env_change)

    # Access environment data
    from axium.core import env
    current_env = env.get_active_env_name()
    prefix = env.get_env_value("prefix")

That's it. No YAML, no hidden state, no user configuration.


⚙️ What Spokes Can Do

Capability Description Example
Register CLI commands Add subcommands to axium axium aws whoami
Hook into events React to daemon or CLI lifecycle on_env_change, on_prefix_apply
Extend prefix logic Register new prefix rules dynamically register_prefix("aws", "aws", "axium run aws")
Intelligent command routing Seamlessly blend spoke + native commands aws whoami (spoke) vs aws s3 ls (native)
Add HUD / Palette elements (Future) Display widgets or menu items register_hud_widget("aws-status")
Run background tasks Schedule or poll data quietly via daemon hooks Example: refresh EC2 list every 60s

🪝 Event System

Axium provides a synchronous event bus for runtime extensibility and coordination between Spokes.

Core Events

Event When it triggers Common use
env_change(new_env, old_env) After axium env set Update context, refresh credentials
spoke_loaded(spoke_name) When a Spoke finishes loading Initialization, set initial HUD state
spoke_reloaded(spoke_name) After axium daemon reload Refresh config, clear caches
daemon_reload() After daemon configuration reloads Reload spoke-specific config
config_reloaded() After all spoke configs are cleared Re-read configuration files
hud_segment_updated(spoke, value) When a HUD segment changes Coordinate related HUD segments
hud_refresh() When HUD should be regenerated Update dynamic HUD segments
gear_loaded(gear_name) When a Gear finishes loading Initialize gear integrations

Events are registered via events.on() and receive arguments as shown. See the Events Reference for complete details on signatures, timing, and best practices.


🧩 Spoke Lifecycle

  1. Load – The daemon scans spokes/ and imports each Spoke.
  2. Register – Calls each Spoke's register(app, events) function.
  3. Activate – Hooks and commands become live.
  4. Reload – On demand (axium daemon reload) without restart.
  5. Unload – When daemon exits or Spoke disables itself.

Spokes can be safely added or removed between reloads — no reboot required.


📦 Directory Layout Example

axium/
  core/
    prefix.py
    env.py
    spokes.py       # Loader
  spokes/
    aws_spoke.py
    terraform_spoke.py
    command_wrapper.py
  ...

🧱 Example: Command Wrapper Spoke

This built-in Spoke handles all prefixed command execution. It defines the axium run CLI command and applies prefix rules before running binaries.

def register(app, events, env):
    @app.command("run")
    def run(cmd: list[str]):
        from axium.core.prefix import apply_prefixes
        full_cmd = apply_prefixes(cmd)
        subprocess.run(full_cmd)

Other Spokes (like AWS or Terraform) simply register prefix metadata — not their own executors.


🔀 Intelligent Command Routing

When a Spoke registers both CLI commands and prefix wrappers, axium run intelligently routes between them:

def register(api):
    # Register spoke commands
    @api.command("whoami")
    def whoami():
        """Show current AWS identity"""
        # Custom spoke logic
        pass

    @api.command("info")
    def info():
        """Show AWS environment info"""
        # Custom spoke logic
        pass

    # Register prefix wrapper for native commands
    api.register_prefix("aws", "aws", "axium run aws")

User Experience:

# Spoke commands route to spoke
aws whoami      # → axium aws whoami (spoke command)
aws info        # → axium aws info (spoke command)

# Native commands get prefix applied
aws s3 ls       # → enva-prod aws s3 ls (native CLI)
aws ec2 describe-instances  # → enva-prod aws ec2... (native CLI)

The routing logic automatically: 1. Checks if the first argument matches a registered spoke subcommand 2. If YES → routes to axium <spoke> <subcommand> 3. If NO → applies prefix wrapper for native command

This allows Spokes to seamlessly extend native CLI tools without requiring users to learn different command patterns.


🚫 What Spokes Should NOT Do

Don’t Reason
Store their own YAML or JSON config Breaks the declarative model
Manage state directly The daemon owns runtime state
Modify core files All extension points go through events or the app API
Depend on other Spokes directly Use event hooks instead

🧩 Data Access for Spokes

Spokes can safely read contextual data through env and core helpers.

env.current() → returns active env key (e.g. "root")
env.get("aws_profile") → returns property from envs.yaml
env.all() → returns full envs.yaml mapping

They can also query or update via IPC if needed (axiumd exposes small APIs).


✅ Contribution Guidelines for Spokes

  1. Keep it stateless — no persistent config or files.
  2. Use register() with (app, events, env) signature.
  3. Prefer declarative config in core (envs.yaml, etc.).
  4. Ensure commands are namespaced (axium aws-*, axium tf-*).
  5. Use events for coordination between Spokes.
  6. Document any external dependencies (e.g., boto3).
  7. Avoid blocking calls — use async when possible.

📦 Official Spokes

The Axium project maintains several official spokes that demonstrate best practices and provide common functionality:

axium-spoke-creds

GitHub

Credential validity checking with HUD integration. Provides commands to check credential status and displays validity in the HUD status line.

Features: - Check credential validity for current environment - Display credential status in HUD (green Y / red N) - Automatic credential refresh hooks - Environment-aware credential management

axium-spoke-aws

GitHub

AWS CLI wrapper with intelligent resource discovery. Simplifies common AWS operations with environment-aware commands.

Features: - EC2 instance listing and management - Resource search across services - Cost analysis commands - Recent resource activity tracking - Environment-specific AWS profile handling


🧠 Design Summary

  • Axium owns all persistent config (YAML, state).
  • Spokes are pure Python runtime extensions.
  • Each Spoke registers commands and event hooks.
  • The daemon orchestrates, reloads, and isolates them.
  • Core provides shared APIs for envs, prefixing, state, and IPC.

Declarative data. Procedural extensions. One consistent model.


This doc complements CONTRIBUTING.md by describing how to author and structure new Spokes.