🧩 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¶
- Load – The daemon scans
spokes/and imports each Spoke. - Register – Calls each Spoke's
register(app, events)function. - Activate – Hooks and commands become live.
- Reload – On demand (
axium daemon reload) without restart. - 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¶
- Keep it stateless — no persistent config or files.
- Use
register()with(app, events, env)signature. - Prefer declarative config in core (
envs.yaml, etc.). - Ensure commands are namespaced (
axium aws-*,axium tf-*). - Use events for coordination between Spokes.
- Document any external dependencies (e.g., boto3).
- 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¶
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¶
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.