TermUI: Terminal UI untuk TypeScript
Tutorial TypeScript Tools #typescript #nodejs #cli #terminal

TermUI: Terminal UI untuk TypeScript

A
Abd. Asis
6 min read
Bagikan:

Membuat CLI yang tampilannya lebih dari sekadar console.log selalu terasa lebih susah dari yang seharusnya. Mau tampilkan spinner? Pasang ora. Butuh tabel? Rakit sendiri dengan karakter box-drawing. Perlu select interaktif? Cari library lain, hope it’s maintained. Hasilnya: package.json penuh dependensi kecil yang masing-masing punya gaya berbeda.

TermUI muncul dengan pendekatan yang berbeda. Alih-alih mengirimkan runtime library, ia mengadopsi model distribusi yang dipopulerkan oleh shadcn/ui: komponen di-copy langsung ke dalam project, jadi sepenuhnya milik kamu. Tidak ada version lock-in, tidak ada black-box dependency — hanya TypeScript yang bisa dibaca dan dimodifikasi sesuka hati.

Dengan 101+ komponen, 8 tema, 12 hooks, dan dukungan adapter untuk library CLI populer, TermUI menjadi layer yang solid untuk membangun tool berbasis terminal menggunakan React mental model yang sudah familiar.

Apa Itu TermUI dan Mengapa Ini Berbeda

TermUI adalah terminal UI framework untuk TypeScript yang dibangun di atas Ink — renderer React untuk terminal. Kalau sudah pernah pakai React, konsepnya langsung terasa familiar: JSX, hooks, dan flexbox, tapi outputnya ke terminal bukan browser.

Yang membedakan TermUI dari Ink biasa adalah lapisan komponen dan distribusinya:

AspekInkTermUI
Komponen bawaan5 primitif101+ komponen siap pakai
Distribusinpm installCopy-paste via CLI (milik kamu)
TemaManual8 tema built-in + custom
Testing-@termui/testing dengan renderToString
Adapter-chalk, ora, commander, inquirer, dll.

Ink tetap menjadi renderer-nya — TermUI tidak menggantikan Ink, melainkan membangun ekosistem komponen di atasnya.

Instalasi dan Setup Proyek

Scaffold proyek baru menggunakan template cli:

npx termui create dashboard-tool --template cli

Masuk ke direktori dan tambahkan komponen yang dibutuhkan:

cd dashboard-tool
npx termui add spinner table select alert

Setiap komponen yang ditambahkan akan di-copy sebagai file TypeScript ke dalam project. Jalankan aplikasi:

npm run dev

Untuk project yang sudah ada, inisialisasi TermUI terlebih dahulu:

npx termui init

Perintah ini akan membuat file konfigurasi dan menyiapkan struktur folder untuk komponen.

Struktur Komponen dan CLI

Interaksi utama dengan TermUI dilakukan lewat CLI npx termui. Jalankan tanpa argumen untuk membuka menu interaktif, atau gunakan perintah langsung:

# Lihat semua komponen yang tersedia
npx termui list

# Tambah beberapa komponen sekaligus
npx termui add spinner progress-bar data-grid

# Tambah semua 101 komponen
npx termui add --all

# Lihat perbedaan antara versi lokal dan registry
npx termui diff spinner

# Preview komponen secara interaktif
npx termui preview

Setelah add, file komponen langsung ada di project. Modifikasi bebas sesuai kebutuhan.

Membangun Antarmuka Pertama

Buat file src/app.tsx dan definisikan antarmuka sederhana menggunakan beberapa komponen:

// src/app.tsx
import React, { useState } from 'react';
import { render } from 'ink';
import { ThemeProvider } from '@termui/core';
import { Spinner, ProgressBar, Alert, Select } from '@termui/components';

interface Task {
  label: string;
  value: string;
}

const availableTasks: Task[] = [
  { value: 'lint', label: 'Jalankan linter' },
  { value: 'test', label: 'Jalankan test suite' },
  { value: 'build', label: 'Build production bundle' },
];

function TaskRunner() {
  const [selected, setSelected] = useState<string | null>(null);
  const [progress, setProgress] = useState(0);
  const [done, setDone] = useState(false);

  const handleSelect = (val: string) => {
    setSelected(val);
    const interval = setInterval(() => {
      setProgress((p) => {
        if (p >= 100) {
          clearInterval(interval);
          setDone(true);
          return 100;
        }
        return p + 10;
      });
    }, 200);
  };

  return (
    <ThemeProvider>
      {!selected && (
        <Select
          options={availableTasks}
          onSubmit={handleSelect}
        />
      )}
      {selected && !done && (
        <>
          <Spinner style="dots" label={`Menjalankan ${selected}...`} />
          <ProgressBar value={progress} total={100} label="Progress" />
        </>
      )}
      {done && (
        <Alert variant="success" title="Selesai">
          Task berhasil dijalankan.
        </Alert>
      )}
    </ThemeProvider>
  );
}

render(<TaskRunner />);

ThemeProvider membungkus seluruh aplikasi dan menyediakan token warna ke semua komponen di dalamnya. Select merender daftar pilihan interaktif langsung di terminal — navigasi dengan arrow key, konfirmasi dengan Enter.

Jalankan dengan:

npx ts-node src/app.tsx

Menggunakan Hooks Bawaan

TermUI menyertakan 12 hooks lewat @termui/core. Beberapa yang paling sering dipakai:

// src/keyboard-demo.tsx
import React, { useState } from 'react';
import { render } from 'ink';
import { ThemeProvider, useInput, useTerminal, useKeymap } from '@termui/core';
import { Text, Box, Heading } from '@termui/components';

function KeyboardMonitor() {
  const [lastKey, setLastKey] = useState('—');
  const { columns, rows } = useTerminal();

  // useInput untuk menangkap keyboard input mentah
  useInput((input, key) => {
    if (key.escape) process.exit(0);
    setLastKey(key.return ? 'Enter' : input || JSON.stringify(key));
  });

  // useKeymap untuk keybinding deklaratif
  useKeymap({
    'ctrl+r': () => setLastKey('[reset]'),
  });

  return (
    <Box flexDirection="column" padding={1}>
      <Heading level={2}>Terminal Monitor</Heading>
      <Text>Ukuran: {columns}x{rows}</Text>
      <Text>Tombol terakhir: <Text bold>{lastKey}</Text></Text>
      <Text dimColor>Tekan Esc untuk keluar</Text>
    </Box>
  );
}

render(
  <ThemeProvider>
    <KeyboardMonitor />
  </ThemeProvider>
);

useTerminal memberikan informasi dimensi terminal secara real-time — berguna untuk layout responsif. useInput menangkap keystroke level rendah, sedangkan useKeymap memungkinkan pendeklarasian keybinding secara lebih ekspresif.

Adapter untuk Library Populer

Salah satu fitur yang langsung terasa berguna adalah adapter. Jika sudah punya codebase yang pakai chalk, ora, atau commander, tidak perlu rewrite dari nol:

// src/adapters-demo.ts

// Drop-in replacement untuk chalk — API identik + integrasi tema TermUI
import chalk from 'termui/chalk';

console.log(chalk.green.bold('Build berhasil'));
console.log(chalk.red('Error:'), chalk.yellow('file tidak ditemukan'));

// Drop-in replacement untuk ora — spinner dengan styling TermUI
import ora from 'termui/ora';

const spinner = ora('Mengambil data...').start();
setTimeout(() => spinner.succeed('Data berhasil dimuat'), 2000);

// Adapter picocolors dengan dukungan hex dan token tema
import pc from 'termui/picocolors';
console.log(pc.bold(pc.cyan('Info:')), 'proses selesai');

Semua adapter mempertahankan API aslinya, jadi migrasi bisa dilakukan bertahap — ganti import satu per satu tanpa mengubah logika aplikasi.

Adapter termui/commander dan termui/inquirer juga tersedia untuk yang sudah membangun CLI dengan Commander.js atau Inquirer.js. Cukup ganti import dan theme otomatis menyesuaikan.

Theming

Ganti tema seluruh aplikasi dengan satu perintah:

npx termui theme dracula
npx termui theme nord
npx termui theme catppuccin

Atau terapkan tema secara programatik untuk kontrol lebih:

import { ThemeProvider, draculaTheme, createTheme } from '@termui/core';

// Buat tema kustom untuk brand tool internal
const internalTheme = createTheme({
  name: 'internal-dark',
  colors: {
    primary: '#00C896',
    focusRing: '#00C896',
  },
});

function App() {
  return (
    <ThemeProvider theme={internalTheme}>
      <YourApp />
    </ThemeProvider>
  );
}

createTheme hanya membutuhkan override dari token yang ingin diubah — sisanya tetap menggunakan default theme sebagai fallback.

Komponen AI untuk CLI Agent

TermUI menyertakan kategori komponen khusus untuk membangun antarmuka AI/LLM, relevan untuk siapa pun yang membangun CLI berbasis agent atau AI assistant:

// src/ai-chat.tsx
import React from 'react';
import { render } from 'ink';
import { ThemeProvider, useChat } from '@termui/core';
import { ChatThread, ChatMessage, TokenUsage } from '@termui/components';
import { useChat as useAiChat } from 'termui/ai';

function ChatInterface() {
  const { messages, isLoading } = useAiChat({ api: '/api/chat' });

  return (
    <ThemeProvider>
      <ChatThread>
        {messages.map((msg, i) => (
          <ChatMessage
            key={i}
            role={msg.role}
            content={msg.content}
          />
        ))}
      </ChatThread>
      <TokenUsage promptTokens={120} completionTokens={85} />
    </ThemeProvider>
  );
}

render(<ChatInterface />);

Komponen seperti ThinkingBlock (untuk chain-of-thought) dan ToolApproval (konfirmasi sebelum tool call dieksekusi) membuat TermUI layak dipertimbangkan untuk membangun antarmuka agent di terminal.

Testing Komponen Terminal

@termui/testing memungkinkan pengujian komponen terminal tanpa perlu terminal sungguhan:

npx termui add --dev testing
// src/__tests__/spinner.test.ts
import { renderToString, screen, waitFor } from '@termui/testing';
import React from 'react';
import { Spinner } from '../components/Spinner';

test('spinner menampilkan label dengan benar', async () => {
  const output = await renderToString(
    React.createElement(Spinner, { style: 'dots', label: 'Memproses data...' })
  );

  expect(screen.hasText('Memproses data...', output)).toBe(true);
});

test('progress bar menampilkan persentase', async () => {
  const output = await renderToString(
    React.createElement('ProgressBar', { value: 60, total: 100, label: 'Upload' })
  );

  expect(screen.hasText('Upload', output)).toBe(true);
});

renderToString merender komponen ke string satu frame — cukup untuk memverifikasi output teks tanpa harus spawn terminal process.

@termui/testing membutuhkan Node.js 18+ dan bekerja paling baik dengan Jest atau Vitest. Pastikan konfigurasi Jest mendukung JSX transform jika komponen menggunakan JSX.

Kesimpulan

TermUI menawarkan apa yang selama ini kurang di ekosistem CLI JavaScript: komponen library yang lengkap dengan distribusi yang memberi ownership penuh kepada developer. Model copy-paste-nya memang terasa tidak konvensional, tapi justru itulah yang membebaskan — tidak ada takut breaking change dari upstream, tidak ada dependensi yang perlu diupdate. Untuk yang membangun internal tool, developer CLI, atau antarmuka agent di terminal, TermUI layak dijadikan fondasi.

Referensi

  1. 1Arindam200/termui — GitHub Repository & README
  2. 2vadimdemedes/ink — React for CLIs (renderer yang digunakan TermUI)
  3. 3TermUI Documentation — Component registry dan API reference

Tentang Penulis

Abd. Asis

Abd. Asis

Software Developer dan Laravel Programmer dari Madura, Indonesia. Passionate tentang PHP, Laravel, dan teknologi web modern.

Komentar