はじめに
CLIツールを作っていると、ただログを流すだけでは物足りなくなる瞬間があります。複数の情報を同時に見せたい、進捗をゲージで表示したい、キー操作でメニューを切り替えたい――そんなとき頭をよぎるのが「ターミナルの中にGUIっぽい画面を作れないか」という発想です。
ncursesを直接触るのは学習コストが高く、Node.jsから使うにも一手間かかります。そこで登場するのがBlessedです。Blessedはncursesに依存せず、純粋なJavaScriptだけでボックス・リスト・フォーム・プログレスバーといった高レベルなウィジェットを組み立てられるターミナルUIライブラリです。
Blessedとは
Blessedは、Node.js向けの高レベルなターミナルインターフェースライブラリです。低レベルなterminfo/termcapの差異を吸収したうえで、CSSのタグ記法に似た書き方で色や罫線を指定でき、マウス操作やキーボードイベントもイベントリスナーとして扱えます。htopやvimのような、画面全体を専有するTUI(Text-based User Interface)アプリケーションを作るのに向いています。
主な特徴
- ncurses非依存 - 純粋なJavaScriptで実装されているため、追加のネイティブ依存やビルド環境を用意しなくてもすぐ使えます
- 高レベルなウィジェット群 - box、list、table、form、textbox、progressbar、logなど、GUIでおなじみの部品が最初から揃っています
- タグによるスタイリング -
{bold}text{/bold}のようなタグで太字や色付けができ、HTML/CSSに近い感覚でレイアウトできます - マウス・キーボード対応 - フォーカス移動やスクロール、クリック検知までイベントとして扱えます
インストール
npm install blessed
yarn add blessed
基本的な使い方
最小構成は「画面(screen)を作り、ウィジェットを追加して、描画する」の3ステップです。以下は中央にボックスを1つ表示するだけのサンプルです。qキーやCtrl+Cで終了できるようにしておくのが定石です。
const blessed = require('blessed');
const screen = blessed.screen({
smartCSR: true,
title: 'blessed demo',
});
const box = blessed.box({
top: 'center',
left: 'center',
width: '50%',
height: '50%',
content: 'Hello {bold}Blessed{/bold}!',
tags: true,
border: { type: 'line' },
style: {
fg: 'white',
bg: 'blue',
border: { fg: '#f0f0f0' },
},
});
screen.append(box);
screen.key(['escape', 'q', 'C-c'], () => process.exit(0));
box.focus();
screen.render();
blessed.screen()が仮想的な画面を、blessed.box()がその上に置くウィジェットを表します。screen.append(box)で描画対象に登録し、最後にscreen.render()を呼ぶことで実際にターミナルへ描画されます。この「append→render」の流れは、他のウィジェットを使う場合でも共通です。
実践的なユースケース
ログビューアを作る
サーバーやバッチ処理のログをただconsole.logで流すと、画面がすぐに埋まって過去の情報を追えなくなります。Blessedのlogウィジェットを使うと、スクロールバック付きの専用ペインにログを溜めつつ、タグで重要度を色分けできます。
const blessed = require('blessed');
const screen = blessed.screen({ smartCSR: true, title: 'log viewer' });
const logBox = blessed.log({
top: 0,
left: 0,
width: '100%',
height: '100%',
border: { type: 'line' },
label: ' Logs ',
scrollback: 1000,
tags: true,
});
screen.append(logBox);
screen.key(['q', 'C-c'], () => process.exit(0));
let count = 0;
setInterval(() => {
logBox.log(`{green-fg}[INFO]{/green-fg} event #${count++} received`);
screen.render();
}, 500);
screen.render();
logBox.log()を呼ぶたびに1行追加され、scrollbackで指定した行数を超えると古い行から自動的に消えていきます。マウスホイールやカーソルキーでスクロールもできるので、流れの速いログでも過去分を確認しやすくなります。
リアルタイムダッシュボードを作る
CPU使用率やメモリ量のような数値を、進捗バーとして常時表示したい場面もあります。progressbarウィジェットとsetIntervalを組み合わせれば、複数のメトリクスを並べたミニダッシュボードが作れます。
const blessed = require('blessed');
const screen = blessed.screen({ smartCSR: true, title: 'dashboard' });
const cpuGauge = blessed.progressbar({
top: 1,
left: 1,
width: '50%-2',
height: 3,
label: ' CPU ',
border: 'line',
style: { bar: { bg: 'green' } },
filled: 0,
});
const memGauge = blessed.progressbar({
top: 1,
left: '50%',
width: '50%-2',
height: 3,
label: ' Memory ',
border: 'line',
style: { bar: { bg: 'blue' } },
filled: 0,
});
screen.append(cpuGauge);
screen.append(memGauge);
screen.key(['q', 'C-c'], () => process.exit(0));
setInterval(() => {
cpuGauge.setProgress(Math.floor(Math.random() * 100));
memGauge.setProgress(Math.floor(Math.random() * 100));
screen.render();
}, 1000);
screen.render();
setProgress(0〜100)を呼ぶだけでバーの塗りつぶし量が変わります。実際の運用ではos.cpus()やprocess.memoryUsage()から取得した値を渡せば、CLI版のリソースモニターとして機能します。
フォーム入力を受け取る
対話的にユーザーからキーワードや設定値を受け取りたい場合は、formとtextbox、buttonを組み合わせます。フォーカス移動とEnterキーでの送信を、GUIのフォームと同じ感覚で組み立てられます。
const blessed = require('blessed');
const screen = blessed.screen({ smartCSR: true, title: 'search' });
const form = blessed.form({
top: 'center',
left: 'center',
width: 40,
height: 8,
border: 'line',
label: ' Search ',
keys: true,
});
const input = blessed.textbox({
parent: form,
top: 1,
left: 1,
width: '90%',
height: 3,
inputOnFocus: true,
border: 'line',
});
const submit = blessed.button({
parent: form,
bottom: 0,
left: 'center',
width: 10,
height: 1,
content: 'Search',
style: { focus: { bg: 'blue' } },
});
submit.on('press', () => form.submit());
form.on('submit', (data) => {
screen.destroy();
console.log('query:', input.getValue());
process.exit(0);
});
screen.key(['C-c'], () => process.exit(0));
input.focus();
screen.render();
inputOnFocus: trueにしておくと、フォーカスが当たった瞬間からキー入力をそのままテキストとして受け付けるようになります。form.on('submit', ...)で送信後の処理を書けば、CLIの対話プロンプトをそのままウィンドウ化したような体験を作れます。
まとめ
Blessedを使うと、ncursesの知識がなくても「ボックスを置く」「リストを並べる」「フォームで入力を受ける」といったGUI的な部品をターミナル上に組み立てられます。ログビューア、リアルタイムダッシュボード、対話フォームと、CLIツールに欲しくなる画面はひととおりカバーされているので、まずは手元の小さなスクリプトに1つウィジェットを足してみるところから試してみてください。