Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Omniterm Laravel Package

pdphilip/omniterm

OmniTerm is a terminal UI toolkit for Laravel that lets you write CLI output as HTML with Tailwind classes and renders it as ANSI. It supports truecolor with 256-color fallback, gradients, arbitrary RGB colors, plus ready-made status badges, tables, progress bars, spinners, and live tasks.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and wire it up in three steps:

composer require pdphilip/omniterm

Then in any Artisan command, add the trait and call $this->omni:

use OmniTerm\HasOmniTerm;

class DeployCommand extends Command
{
    use HasOmniTerm;
    protected $signature = 'deploy';

    public function handle()
    {
        $this->omni->titleBar('Deploying', 'sky');
        $this->omni->success('Assets built');
        $this->omni->info('Running migrations...');
        $this->omni->error('Queue worker not running');
    }
}

Where to look first: Rather than reading the README end-to-end, copy the bundled samples into your app — they double as a working reference and a live test harness:

mkdir -p app/Console/Commands/OmniTermSamples
cp vendor/pdphilip/omniterm/samples/Commands/*.php app/Console/Commands/OmniTermSamples/
# Fix the namespace to App\Console\Commands\OmniTermSamples, then:
php artisan omniterm:full-demo

Your first real use case: the low-risk, high-value entry point is status messages and tables in an existing long-running command (a deploy script, an import command, a sync job). You get immediate polish for ~5 lines of change, with zero coupling to the rendering engine. Everything else (progress bars, live tasks, the browser) can be layered in later once you trust the output on your target terminals.

Implementation Patterns

Builder-style progress for loops with known cardinality. The progressBar() builder is fluent and the terminal is redrawn in place. Keep the bar object out of the loop body — instantiate once, advance() per iteration, finish() at the end. For unknown-length work, switch to a loader/spinner instead.

$bar = $this->omni->progressBar($users->count())->framed()->steps();
$bar->start();
foreach ($users as $user) { $this->sync($user); $bar->advance(); }
$bar->finish();

Background work with runTask for unpredictable durations. Any callback returning ['state' => ..., 'message' => ...] becomes an animated async task. This is the right tool when you can't compute a percentage — network calls, external API batches, archive extraction.

liveTask for chunked pipelines with counters. The ->row() + ->run() + ->increment() + ->finish() pattern is ideal for ETL/import commands where you want "Created / Updated / Skipped / Failed" tallies to update per chunk. Note the callbacks return keyed arrays that you feed into increment() manually — OmniTerm doesn't auto-bind results to rows.

Escape hatch: render raw HTML when a built-in component doesn't fit. This is the real power move. Everything OmniTerm ships is Blade→HTML→ANSI, so you can build project-specific output with Tailwind classes and reuse it:

$this->omni->render(<<<HTML
<div class="flex">
  <span class="bg-emerald-600 text-white font-bold px-1">OK</span>
  <span class="flex-1 text-zinc-400 px-1">Cache warmed</span>
  <span class="text-zinc-600 text-right w-10">{$ms}ms</span>
</div>
HTML);

Wrap recurring layouts as Blade views and call $this->omni->view('cli.deploy-status', [...]) — that's how the built-in components themselves are implemented.

Interactive browsing for dashboards and pickers. $this->omni->browse() returns the selected key or null on Esc; closures receive an OmniTerm instance so each row can render full omni output. Good for status dashboards, log viewers, or choosing a resource before acting on it.

Integration tips:

  • Put HasOmniTerm on a base Command class if you have many commands, so $this->omni is always available.
  • In CI or non-TTY contexts, guard interactive calls (browse, ask) behind a check — OmniTerm renders ANSI but a split-pane TUI is pointless when piped.
  • parse() returns the ANSI string without printing — useful for testing output format or writing to a log file with color preserved.

Gotchas and Tips

Supported Tailwind is a strict subset, not the real thing. The README's class tables are exhaustive: no gap-*, no p-{n} (only px/pl/pr), no borders, no w-full, no arbitrary h-*. When a class "does nothing," it's because it isn't implemented. Cross-check against the supported-classes table before debugging — and be aware the Tailwind "compile" here is a hand-rolled parser, so unexpected nesting of flex/flex-1 inside wrappers can produce surprising widths.

Width math is character-based, and flex-1 is how you fill space. Use flex-1 on the stretchy child and w-{n} on fixed columns; content-repeat-[char] fills a width with a repeated glyph (typically box-drawing ─). Getting table layouts right means thinking in terminals columns, not CSS boxes.

Truecolor auto-detection can bite you. OmniTerm falls back from 16M to 256-color on older terminals (notably Apple Terminal). If your carefully chosen bg-[R,G,B] colors look wrong, that's the fallback quantizing them. There's no documented config override in the README — verify appearance in the specific terminal(s) your team uses rather than assuming iTerm2 fidelity everywhere.

liveView requires explicit cleanup. Call $this->omni->endLiveView() when done. Forget it and the cursor state/redraw line can be left in a broken position, especially if an exception escapes the loop — wrap the live section in try/finally.

Async tasks use a background process — callbacks run out-of-process. That means: no sharing Laravel's in-memory container state with the parent, DB connections may need re-establishing, and returning arbitrary objects won't work (return keyed arrays). Keep task callbacks small and self-contained. The runTask contract expects ['state' => 'success'|...] keys for the spinner to render correctly.

V3 dropped Laravel 10 and Symfony 6. If you're on Laravel 10 or a pre-7 Symfony, you need the previous major. Also check symfony/console constraints against anything in your dependency graph that pins ^6.x — v3 bumps to ^7.0||^8.0.

Extension points worth exploiting:

  • terminal()->getWidth() for responsive layouts — decide column widths or whether to render a compact vs. full table based on real terminal width.
  • Blade views as first-class citizens — build a small resources/views/cli/ library of your own status/table components, then use $this->omni->view() everywhere. This is how you avoid copy-pasted HTML across commands.
  • parse() for testability — assert on ANSI output structure in Pest tests instead of eyeballing terminal output.

Debugging output that looks wrong: start with render() on the exact same HTML in the tailwind-classes sample's context. If a class appears unsupported there too, it's not your markup — it's the parser. If it works there but not in your view, you're likely passing a variable into a class name that produces a string the parser doesn't expect (it matches literal class tokens, so class="bg-{{ $color }}-600" only works if the resulting token matches a known color/shade pair).

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource