Skalabilitas utilitas baris perintah internal (internal tooling) sering kali terbentur oleh dua masalah utama: import bloat yang memperlambat waktu startup aplikasi dan antarmuka konsol yang kaku. Menggabungkan library Rich untuk formatting teks/tabel dan Textual untuk Terminal User Interface (TUI) asinkron memberikan solusi modern tanpa mengorbankan performa sistem.

Arsitektur Modular Berbasis Command Pattern

Pola desain Command Pattern memisahkan antarmuka deklaratif modul dari logika eksekusi intinya. Setiap tool didaftarkan sebagai objek mandiri yang mengimplementasikan protocol seragam. Ini mencegah ketergantungan erat (tight coupling) antar utilitas.

from typing import Protocol, runtime_checkable
from rich.console import Console

@runtime_checkable
class ToolCommand(Protocol):
    name: str
    description: str

    def execute(self, console: Console, *args: str) -> int:
        ...

Dynamic Module Loading Tanpa Import Bloat

Memuat semua library berat (seperti SDK cloud, konektor database, atau parser log) secara langsung di file konfigurasi utama membuat CLI lambat dibuka. Solusinya adalah memuat metadata modul terlebih dahulu, kemudian melakukan lazy importing menggunakan pustaka bawaan importlib saat perintah dipilih.

import importlib
from typing import Dict, Type
from app.core.protocol import ToolCommand

REGISTRY: Dict[str, str] = {
    "db-ping": "app.tools.db:DatabasePingTool",
    "k8s-logs": "app.tools.k8s:KubernetesLogTool",
}

def load_tool(command_name: str) -> ToolCommand:
    if command_name not in REGISTRY:
        raise ValueError(f"Perintah '{command_name}' tidak terdaftar.")
    
    module_path, class_name = REGISTRY[command_name].split(":")
    module = importlib.import_module(module_path)
    command_class: Type[ToolCommand] = getattr(module, class_name)
    return command_class()

Pendekatan ini membatasi pemrosesan I/O dan eksekusi bytecode hanya pada pustaka yang benar-benar dipanggil pengguna pada sesi aktif.

Antarmuka Konsol Interaktif Menggunakan Textual dan Rich

Textual menangani perulangan event (event loop), layout terminal reaktif, serta binding keyboard, sementara Rich menangani perenderan teks berformat, status spinner, dan tabel output di dalam widget Textual.

from textual.app import App, ComposeResult
from textual.widgets import Header, Footer, ListView, ListItem, Label, Log
from rich.table import Table

class MultitoolTUI(App):
    BINDINGS = [("q", "quit", "Keluar"), ("r", "run_selected", "Eksekusi")]

    def compose(self) -> ComposeResult:
        yield Header()
        yield ListView(
            ListItem(Label("db-ping"), id="db-ping"),
            ListItem(Label("k8s-logs"), id="k8s-logs"),
            id="tool-list"
        )
        yield Log(id="console-log")
        yield Footer()

    def on_list_view_selected(self, event: ListView.Selected) -> None:
        log_widget = self.query_one("#console-log", Log)
        selected_id = event.item.id
        log_widget.write_line(f"Menyiapkan eksekusi untuk modul: {selected_id}")

Validasi dan Update Metadata Internal Tool Secara Aman

Jika multitool mengambil daftar modul atau metadata konfigurasi dari endpoint remote (misalnya internal artifact repository), integritas skema wajib divalidasi guna mencegah eksekusi kode berbahaya atau konfigurasi korup. Validasi dapat diimplementasikan menggunakan schema guard ketat dengan tipe data Python baku.

import json
import urllib.request
from dataclasses import dataclass

@dataclass(frozen=True)
class ToolMetadata:
    version: str
    checksum: str
    enabled: bool

def fetch_validated_metadata(endpoint_url: str) -> ToolMetadata:
    req = urllib.request.Request(endpoint_url, headers={"User-Agent": "InternalCLI/1.0"})
    with urllib.request.urlopen(req, timeout=5) as response:
        if response.status != 200:
            raise RuntimeError(f"Gagal mengambil data, status HTTP: {response.status}")
        raw_data = json.loads(response.read().decode("utf-8"))
    
    # Validasi skema eksplisit
    if not isinstance(raw_data.get("version"), str) or not isinstance(raw_data.get("checksum"), str):
        raise ValueError("Payload metadata tidak memenuhi skema yang valid.")
    
    return ToolMetadata(
        version=raw_data["version"],
        checksum=raw_data["checksum"],
        enabled=bool(raw_data.get("enabled", False))
    )

Unit Testing Runner CLI

Runner perintah harus diuji secara terisolasi tanpa memerlukan rendering terminal penuh. Gunakan output stream in-memory Rich untuk memverifikasi routing command dan penanganan exception.

import io
from rich.console import Console

def test_tool_runner_execution():
    test_buffer = io.StringIO()
    mock_console = Console(file=test_buffer, force_terminal=False)

    class DummyTool:
        name = "test-cmd"
        description = "Test command runner"
        def execute(self, console: Console, *args: str) -> int:
            console.print("Runner executed successfully")
            return 0

    tool = DummyTool()
    exit_code = tool.execute(mock_console)

    assert exit_code == 0
    assert "Runner executed successfully" in test_buffer.getvalue()

if __name__ == "__main__":
    test_tool_runner_execution()
    print("Semua assert test berhasil lolos.")

Mitigasi Risiko dan Best Practice

  • Fail-Fast Error Boundaries: Tangani kegagalan impor modul pada level registry dengan fallback visual yang jelas agar kerusakan satu modul tidak membuat keseluruhan CLI crash.
  • Subprocess Safety: Jika multitool menjalankan binary lokal eksternal, gunakan subprocess.run dengan shell=False dan teruskan argumen via array untuk mitigasi command injection.
  • Terminal Reset: Pastikan exception yang tidak tertangani mengeksekusi teardown terminal dengan benar agar state terminal pengguna tidak rusak setelah CLI ditutup mendadak.