pdphilip / omniterm
This is my package OmniTerm
Requires
- php: ^8.2
- ext-dom: *
- ext-libxml: *
- illuminate/contracts: ^11.0||^12.0||^13.0
- laravel/prompts: ^0.2||^0.3
- spatie/laravel-package-tools: ^1.16
- symfony/console: ^7.0||^8.0
Requires (Dev)
- larastan/larastan: ^2.9||^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.1.1
- orchestra/testbench: ^9.0||^10.0||^11.0
- pestphp/pest: ^2.34||^3.0||^4.0
- pestphp/pest-plugin-arch: ^2.7||^3.0||^4.0
- pestphp/pest-plugin-laravel: ^2.3||^3.0||^4.0
- phpstan/extension-installer: ^1.3||^2.0
- phpstan/phpstan-deprecation-rules: ^1.1||^2.0
- phpstan/phpstan-phpunit: ^1.3||^2.0
README
Terminal UI toolkit for Laravel
Rich CLI output using HTML and Tailwind CSS classes, rendered as ANSI in your terminal.

About
OmniTerm is a terminal rendering engine for Laravel. Write your CLI output as HTML with Tailwind CSS classes and OmniTerm compiles it to ANSI escape sequences.
The Tailwind-for-terminal concept was pioneered by Termwind. OmniTerm builds on that idea with its own rendering engine that adds:
- 16 million color support - full RGB truecolor, with automatic 256-color fallback for older terminals
- Gradients -
bg-gradient-to-r,from-{color},via-{color},to-{color}for smooth per-character color transitions - Arbitrary RGB classes -
text-[R,G,B]andbg-[R,G,B]for computed/dynamic colors - Content repeat -
content-repeat-[char]to fill widths with box-drawing characters
On top of the rendering engine, OmniTerm ships with a set of pre-built components for common CLI patterns: status messages, data tables, progress bars, spinners, live tasks, and an interactive split-pane browser.
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
Installation
composer require pdphilip/omniterm
Built-in Components
Add the HasOmniTerm trait to any Artisan command:
use OmniTerm\HasOmniTerm;
class MyCommand extends Command
{
use HasOmniTerm;
public function handle()
{
$this->omni->success('Ready');
}
}
Status Messages
One-line status badges:
$this->omni->success('Task completed'); // Green GOOD badge
$this->omni->error('Something went wrong'); // Red FAIL badge
$this->omni->warning('Check your config'); // Amber WARN badge
$this->omni->info('Processing...'); // Blue INFO badge
$this->omni->disabled('Feature off'); // Gray OFF badge
Detailed status blocks with title, message, and help lines:
$this->omni->statusSuccess('Migration Complete', 'All 42 records processed', ['Run cache:clear']);
$this->omni->statusError('Connection Failed', 'Could not reach database', ['Check .env', 'Ensure MySQL is running']);

Data Tables
Key-value rows with status indicators:
$this->omni->tableHeader('Setting', 'Value', 'Notes');
$this->omni->tableRow('Database', 'mysql', 'Production server');
$this->omni->tableRowSuccess('Connection', 'Active');
$this->omni->tableRowError('SSL Certificate', 'Expired');
$this->omni->tableRowWarning('Memory', '85% used');

Visual Elements
Title bars, boxes, and horizontal rules:
$this->omni->titleBar('My Application', 'sky');
$this->omni->roundedBox('Welcome', 'text-cyan-500', 'text-white');
$this->omni->hr();
$this->omni->hrSuccess();

Progress Bars
Fluent builder API with three styles: simple, framed, and gradient. Color steps transition from rose through amber to emerald as progress increases.
$bar = $this->omni->progressBar(100)->framed()->steps();
$bar->start();
foreach ($items as $item) {
// work...
$bar->advance();
}
$bar->finish();
Other variants:
$this->omni->progressBar(50); // Simple bar (sky)
$this->omni->progressBar(50)->steps(); // Simple with color steps
$this->omni->progressBar(50)->framed()->color('indigo'); // Framed with custom color
$this->omni->progressBar(50)->gradient(); // Gradient (amber → emerald)
$this->omni->progressBar(50)->framed()->gradient('rose', 'sky'); // Custom gradient

Live Tasks
Run a callback in a background process with an animated spinner:
use OmniTerm\Async\Spinner;
$this->omni->newLoader(Spinner::Sand);
$result = $this->omni->runTask('Processing data...', function () {
sleep(3);
return ['state' => 'success', 'message' => 'Done'];
});
One-liner with task():
$this->omni->task('Processing batch job', function () {
sleep(3);
return ['state' => 'success', 'message' => 'Batch complete', 'details' => '500 records'];
}, Spinner::DotsCircle, ['text-indigo-500', 'text-violet-500']);
For fine-grained control with live-updating counters:
$task = $this->omni->liveTask('Processing records', spinner: Spinner::Dots3)
->row('Created', 0, 'text-sky-500')
->row('Updated', 0, 'text-emerald-500')
->row('Skipped', 0, 'text-amber-500')
->row('Failed', 0, 'text-rose-500');
// Simulate 5 chunked batches
for ($batch = 0; $batch < 5; $batch++) {
$result = $task->run(function () {
usleep(800000);
return [
'created' => rand(10, 50),
'updated' => rand(5, 20),
'skipped' => rand(0, 5),
'failed' => rand(0, 2),
];
});
$task->increment('Created', $result['created']);
$task->increment('Updated', $result['updated']);
$task->increment('Skipped', $result['skipped']);
$task->increment('Failed', $result['failed']);
}
$task->finish('Processing complete');
The Spinner enum provides 10 animation types: Dots, Dots2, Dots3, DotsCircle, Sand, Clock, Material, Pong, Progress, ProgressLoader.
Interactive Browser
Split-pane TUI: scrollable list on the left, detail view on the right. Items can be closures (rendered with full omni output), associative arrays (auto-formatted), or plain arrays.
+-- Server Dashboard ---------------+-----------------------------------+
| > web-01 | ✓ GOOD Healthy |
| web-02 | All checks passing |
| db-primary | |
| cache-01 | Metric Value |
+-----------------------------------+ CPU 23% |
+-----------------------------------+
↑/↓ Navigate Enter Select q/Esc Exit
use OmniTerm\OmniTerm;
$selected = $this->omni->browse('Server Dashboard', [
'web-01' => function (OmniTerm $omni) {
$omni->statusSuccess('Healthy', 'All checks passing');
$omni->tableHeader('Metric', 'Value');
$omni->tableRowSuccess('CPU', '23%');
$omni->tableRow('Memory', '4.2 GB / 8 GB');
},
'db-primary' => ['status' => 'running', 'cpu' => '45%', 'memory' => '28 GB / 32 GB'],
]);
// Returns selected key, or null on Esc
Interactive Prompts
$name = $this->omni->ask('What is your name?');
$color = $this->omni->ask('Choose a color:', ['red', 'green', 'blue']);
DIY - The Rendering Engine
The built-in components are just Blade templates compiled through OmniTerm's rendering engine. You can use the same engine directly to build anything.
render()
Write HTML with Tailwind classes, get ANSI output:
$this->omni->render('<div class="flex">
<span class="bg-emerald-600 text-white font-bold px-1">PASS</span>
<span class="flex-1 text-zinc-400 px-1">Database connection verified</span>
<span class="text-zinc-600 text-right w-12">12ms</span>
</div>');
liveView()
Redraws in place, for live-updating displays:
$live = $this->omni->liveView('<div>Starting...</div>');
for ($i = 1; $i <= 100; $i++) {
$live->reRender("<div>Progress: {$i}%</div>");
usleep(50000);
}
$this->omni->endLiveView();
parse()
Convert HTML to an ANSI string without printing:
$ansi = $this->omni->parse('<span class="text-sky-500">Hello</span>');
terminal()
Terminal dimensions:
$width = $this->omni->terminal()->getWidth();
$height = $this->omni->terminal()->getHeight();
Supported Classes

Layout
| Class | Effect |
|---|---|
flex | Horizontal layout |
flex-1 | Fill remaining space |
w-{n} | Fixed width in characters |
space-x-{n} | Gap between children |
Spacing
| Class | Effect |
|---|---|
px-{n}, pl-{n}, pr-{n} | Horizontal padding |
mx-{n}, ml-{n}, mr-{n} | Horizontal margin |
mt-{n}, mb-{n} | Vertical margin (blank lines) |
Typography
| Class | Effect |
|---|---|
font-bold | Bold |
text-center | Center-align |
text-right | Right-align |
Colors
All Tailwind colors with shades 50-950: slate, gray, zinc, neutral, stone, red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose.
| Class | Effect |
|---|---|
text-{color}-{shade} | Text color, e.g. text-sky-500 |
bg-{color}-{shade} | Background color, e.g. bg-red-600 |
text-[R,G,B] | Arbitrary RGB text, e.g. text-[255,100,50] |
bg-[R,G,B] | Arbitrary RGB background |
Gradients
Per-character color interpolation across an element's width.
| Class | Effect |
|---|---|
bg-gradient-to-r | Left-to-right gradient |
bg-gradient-to-l | Right-to-left gradient |
from-{color}-{shade} | Start color |
via-{color}-{shade} | Midpoint color |
to-{color}-{shade} | End color |
$this->omni->render('<div class="flex">
<span class="flex-1 bg-gradient-to-r from-indigo-800 via-purple-500 to-pink-400 text-white text-center">
Smooth gradient
</span>
</div>');
Content
| Class | Effect |
|---|---|
content-repeat-[char] | Repeat character to fill width |
Color Mode Detection
OmniTerm auto-detects your terminal's color capability:
- Truecolor (16M) - full RGB. iTerm2, Kitty, WezTerm, most modern terminals.
- 256-color - automatic fallback for older terminals (e.g. Apple Terminal). Colors mapped to nearest match.
No configuration needed.
Using Blade Templates
Since OmniTerm is a Laravel package, you can write your CLI output as Blade views and render them through the engine. This is how all the built-in components work:
// resources/views/cli/deploy-status.blade.php
<div class="flex">
<span class="bg-{{ $color }}-600 text-white font-bold px-1">{{ $badge }}</span>
<span class="flex-1 text-zinc-400 px-1">{{ $message }}</span>
</div>
// In your command
$this->omni->view('cli.deploy-status', [
'badge' => 'DEPLOY',
'color' => 'emerald',
'message' => 'Production updated',
]);
Samples
OmniTerm includes sample commands for every feature. Copy them into your app:
mkdir -p app/Console/Commands/OmniTermSamples
cp vendor/pdphilip/omniterm/samples/Commands/*.php app/Console/Commands/OmniTermSamples/
Update the namespace in each file to App\Console\Commands\OmniTermSamples, then:
php artisan omniterm:full-demo # One of every feature
php artisan omniterm:status-messages # Status messages
php artisan omniterm:data-tables # Key-value tables
php artisan omniterm:visual-elements # Boxes and horizontal rules
php artisan omniterm:title-bars # Title bar colors
php artisan omniterm:progress-bars # All progress bar styles
php artisan omniterm:spinners # All 10 spinner animations
php artisan omniterm:async-tasks # Async task execution
php artisan omniterm:live-task-demo # LiveTask with feedback rows
php artisan omniterm:browser-demo # Interactive split-pane browser
php artisan omniterm:tailwind-classes # Every supported CSS class
php artisan omniterm:interactive # Interactive prompts
php artisan omniterm:custom-colors # Custom color schemes
php artisan omniterm:global-functions # Render & live view functions
Testing
composer test # Lint + PHPStan + Pest
composer test:unit # Pest only
composer types # PHPStan only
composer format # Laravel Pint
License
MIT. See License File.