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

Web Terminal Stream Laravel Package

mwguerra/web-terminal-stream

View on GitHub
Deep Wiki
Context7
1.1.2

Fixed

  • No audit row was ever written in an application whose users are keyed by ULID or UUID. TerminalLogger::getUserId() was declared ?int and returned auth()->id(), which is whatever the application's user key is. Under declare(strict_types=1) a string key made the return itself raise a TypeError — inside createLog(), whose catch (\Throwable) swallowed it while the comment claimed the table might not exist. The audit trail was therefore silently empty, and nothing anywhere said why. Reproduced on a ULID-keyed panel: a real root shell was opened through the terminal and terminal_stream_logs stayed at zero rows.

    getUserId() now returns int|string|null, logServerDisconnection() accepts int|string|null for $userId, and TerminalLog::scopeForUser() accepts int|string. No migration is needed — the published stub already lets the host application choose its own user-key column type.

  • createLog() no longer swallows every failure in silence. A missing table (the migration not being run yet) is still tolerated, because that is a legitimate state during installation — but it is now logged as a warning instead of vanishing. Anything else is logged as an error: a bug in this package or its configuration must not present as "there was nothing to log". That blanket catch is precisely what hid the type error above for an entire release cycle.

1.1.1

Performance

  • Round-trip latency no longer scales with the number of open terminals. The server asked every session, every 10ms, whether it had output, and each SSH read waited out its own timeout even when the answer was "no" — so an IDLE terminal cost as much as a busy one and latency grew linearly with the fleet. Each session's transport is now registered with the event loop, which wakes the server exactly when bytes arrive.

    Measured on one machine, 100 concurrent sessions, before → after:

    before after
    round-trip p50 1877 ms 264 ms 7.1×
    round-trip p95 2116 ms 359 ms 5.9×
    connect p50 17866 ms 3637 ms 4.9×
    connect p95 57206 ms 7959 ms 7.2×

    Establishment improved as a side effect: the serial connect no longer competes with a polling sweep that grew with every session already up.

    A slow backstop sweep still runs (stream.backstop_sweep_seconds, default 250ms) because phpseclib can hold decoded bytes after its socket stops being readable — a missed wakeup must degrade to "slightly late", never to "silent". Set stream.io_mode=poll to restore the previous behaviour without downgrading.

  • terminal-stream:serve --workers=N runs N servers sharing the port via SO_REUSEPORT, supervised (a worker that dies is replaced; SIGTERM/SIGINT are forwarded for a graceful fleet shutdown). SSH connect + auth is still synchronous on its own loop — this does not change that — but a stalling handshake now freezes 1/N of the fleet instead of all of it, and the phpseclib crypto spreads across cores. Default remains 1, so upgrading changes nothing until you ask for more.

    max_connections and max_sessions_per_user are enforced per worker, since workers share nothing: 4 workers × 100 is a fleet ceiling of 400.

Added

  • Fleet metrics + a Filament widget. Each worker publishes a snapshot (live sessions by type, SSH connect p50/p95, loop sweep p50/p95, refusals by reason, uptime); Metrics\MetricsReader aggregates them and Filament\Widgets\TerminalServerHealthWidget renders them.

    Two aggregation choices worth knowing: the fleet reports the worst worker's latency rather than the average (a fleet is only as good as its slowest loop, and averaging hides the worker that is stalling users), and capacity is derived from workers actually alive, so a dead worker cannot contribute phantom headroom. Percentiles with no samples report null, never 0 — an unmeasured latency must not read as an excellent one.

    New config: stream.metrics_interval_seconds (5), metrics.stale_after_seconds (30).

Fixed

  • PtySessionRegistry lost sessions under concurrent writers. LOCK_EX guarded the write but not the read-modify-write around it, so two processes could read the same state and the later write dropped the earlier one's entries. Harmless while the server was one process; with --workers it is severe — measured, 8 concurrent writers registering 96 sessions left 4. The whole cycle now runs under flock.

  • The green window control lifted a pane to fullscreen by toggling a class imperatively, which Livewire's DOM morph then stripped on the next round trip. The class is Alpine-bound instead, so fullscreen survives (re)connects.

  • The unit suite could not run, and had been failing unnoticed since 1.1.0. Three faults stacked:

    ConnectionVault (1.1.0) routes every connection config through the cache, so mounting a terminal now touches the cache store — but the test environment left Laravel's default database store pointing at Testbench's in-memory SQLite, where no cache table exists. 128 tests died on no such table: cache. TestCase now selects the array store; IntegrationTestCase keeps file, which its separately-spawned server needs.

    The e2e host app scaffolded into tests/e2e-app put ~17k files inside the tree Pest scans for datasets on every boot. The suite hung before printing a line, and each --parallel worker exhausted the default 128M limit — which is why the failures above stayed invisible. The app now scaffolds to .e2e-app at the repo root (scripts/e2e/setup.sh, scripts/e2e/run.sh, playwright.config.js follow it). Unit suite: >10 min hang → under 3 s.

    nullConnection() lived in one spec and was called from another. One process serially, so it always resolved; under --parallel the two files landed in different workers and four tests died on Call to undefined function. It moved to tests/Pest.php with the other shared helpers.

    Now green both ways: 488 unit (serial and --parallel), 10 integration + 1 Linux-only skip, PHPStan clean.

Changed

  • The three title-bar dots do something. They were decorative <span>s; they are now labelled, keyboard-reachable buttons with macOS semantics. Red and yellow ask the container to close the window (a dashboard toggles the source off — so it stays reopenable from the bar; a workspace closes the pane, refusing when it is the last one). Green toggles fullscreen locally, so it works in a dashboard, a workspace, or standalone. A terminal with no container renders the close dots dimmed and aria-disabled rather than pretending. Escape is deliberately NOT bound to exit fullscreen: the terminal forwards it to the PTY, and stealing it would break vim, less, and anything modal inside the shell.

  • Rounded panes no longer show a square corner behind them. The pane container paints the divider colour and had no radius, so it showed through at the four corners the pane rounds away. It is now clipped to the same silhouette, using the same utility the pane uses so the two cannot drift; inner dividers are unaffected.

1.1.0

Security

  • The resolved connection config — including SSH private keys — was rendered into the page HTML. Components held it in a #[Locked] public array $connectionConfig. #[Locked] stops the client writing a property back; it does not keep it off the wire, and Livewire serializes every public property into the wire:snapshot attribute it renders. So the SSH host, port, username, passphrase and private key were present in the delivered HTML of every StreamTerminal, every StreamWorkspace pane (via panes/paneDefaults/paneTemplate), and every StreamDashboard source — including sources the user had never opened. Anyone who could view the page, its cache, or a screen share could read the credentials for every configured target.

    Configs are now held server-side by Security\ConnectionVault: encrypted in the cache, keyed by the SHA-256 of a 64-char random handle, and bound to the identity that minted it, so a handle scraped from one user's page resolves to nothing for anyone else. Components carry only the handle (connectionRef). Custody is taken at Concerns\ResolvesTerminalProperties — the single author of the prop-set contract — plus at each container's mount for prop sets passed in directly, so no path can hand a raw config to Livewire state.

    Handles expire on an idle window (stream.connection_ttl, default 7200s, sliding on each read). A lapsed handle makes the terminal report that it needs a reload; it never falls through to a default connection, because an empty config reads as "local shell" to the layers below.

  • POST /terminal-stream/ws-token can now refuse client-supplied connection configs. That endpoint accepts a whole config from the caller, so with an empty security.ssh_allowed_hosts any caller past the Gate can use the app server as an SSH/SSRF pivot. It now prefers a connectionRef, and the raw-config path can be switched off with security.allow_client_supplied_connections (default true — unchanged behaviour; set it to false when only the schema components are used).

Added

  • Security\ConnectionVault — encrypted, identity-bound, sliding-window custody for resolved connection configs.
  • Concerns\VaultsPaneConnections — normalizes any pane prop set handed to a container so a raw config can't become component state.
  • Config: stream.connection_ttl (WEB_TERMINAL_STREAM_CONNECTION_TTL, default 7200) and security.allow_client_supplied_connections (WEB_TERMINAL_STREAM_ALLOW_CLIENT_CONNECTIONS, default true).

Changed

  • Prop sets carry connectionRef instead of connectionConfig. The fluent API is untouched — ->ssh(...), ->local() and [@livewire](https://github.com/livewire)('web-terminal-stream', ['connectionConfig' => [...]]) all still work, since mount still accepts a raw array and vaults it. Only reads move: $component->connectionConfig becomes $component->connectionConfig(), and $props['connectionConfig'] becomes app(ConnectionVault::class)->get($props['connectionRef']).
  • StreamTerminal gained a connectionType property carrying just 'local'/'ssh'. It is not a secret, the chrome reads it every render, and keeping it out of the vault means a lapsed handle still renders a coherent terminal.
1.0.1

Changed

  • README images now use absolute raw.githubusercontent.com URLs so they render on Packagist and the Filament plugins page (which don't resolve repo-relative paths); the hero image carries filament-hidden to avoid duplicating the plugin-page banner.
  • Installation instructions updated for Packagist (composer require mwguerra/web-terminal-stream) — the pre-publication private-VCS-repository instructions were removed.
  • Added the Filament plugins directory banner (docs/images/mwguerra-web-terminal-stream.jpg, 16:9 2880×1620 JPEG). Docs-only; docs/ is export-ignored, so dist archives are unchanged.
1.0.0

First stable release.

Security

  • Connection authorization + token issuance hardening. Http\Controllers\TerminalWebSocketController previously minted a WebSocket token for any client-supplied connectionConfig with no gate and no restriction on the target, letting an authenticated user open an arbitrary local shell or SSH connection through the server. Issuance (both the REST route and the Livewire path) now (1) enforces the optional useStreamTerminal gate, (2) runs a new Security\ConnectionPolicy — security.allow_local toggles local shells, security.ssh_allowed_hosts is an SSH destination allow-list (bare host or host:port) — and (3) rate-limits the ws-token route (security.token_rate_limit). The policy is re-checked on the WebSocket server before a PTY starts, so a token minted for a disallowed target is still refused. The cached connection config (which may hold SSH credentials) is now encrypted at rest and decrypted by the server on pull.
  • SSH host-key verification. phpseclib performs the key exchange but trusts any server host key, so outbound SSH sessions were open to a man-in-the-middle. Security\SshHostKeyVerifier verifies the host key before authentication, controlled by security.ssh_host_key.mode: off (documented insecure default), fingerprints (SHA256/MD5 pins per host or host:port), or known_hosts (an OpenSSH file, plaintext and hashed |1| entries, [host]:port for non-standard ports). A mismatch aborts the connection.
  • The WebSocket server now validates the Origin header of every browser handshake against the (re-added) stream.allowed_origins config key (default [env('APP_URL', 'http://localhost')]). Disallowed origins are rejected with HTTP 403 — and logged at warning level — before the single-use auth token is consumed. Requests without an Origin header (non-browser clients) still pass through to token auth; a literal '*' entry (or an empty list) disables the check.

Added

  • Connection stress harness (composer stress). The first registered benchmark: 100 concurrent token-authenticated WebSocket→SSH-PTY sessions (deterministic 3–10s lifetimes, remotely-expanded echo markers proving real round-trips) swept across latency profiles — Toxiproxy on the client leg (client-10ms/client-500ms) and tc netem on the sshd egress (ssh-10ms/ssh-500ms) plus a baseline — with an over-cap probe asserting stream.max_connections refusals (HTTP 503) and a PtySessionRegistry drain check after every run. Results merge into docs/benchmarks/stress-latest.json. Opt-in via WTS_STRESS=1; never runs in CI or composer bench. New dev dependency: ratchet/pawl.
  • grid.spec.js e2e coverage for TerminalGrid — side-by-side columns(2) geometry, per-pane WebSocket output isolation, and the pure-CSS :focus-within focus ring following clicks between panes.
  • WebSocket server resilience limits. The long-running server is a single process holding one PTY per connection, so unbounded growth is a denial-of-service surface. New stream config knobs: max_connections (total live PTYs, default 100), max_sessions_per_user (per authenticated user, default 10), and max_handshake_bytes (caps the pre-handshake buffer, default 16 KB). The server also reaps a session whose shell exited (previously it leaked in the bridge map forever and never told the client), closes sockets whose PTY outlived max_session_lifetime, tears down every PTY/SSH channel on SIGINT/SIGTERM (needs ext-pcntl), and records each PID's kernel start-time so a stale-session reap never signals a recycled PID belonging to an unrelated process.
  • terminal-stream:serve capability preflight. On boot the server warns when ext-posix is missing (local-shell PTY resize + orphan cleanup disabled), ext-pcntl is missing (no graceful shutdown), or the host is not Linux (local PTY resize relies on /proc + stty). ext-posix / ext-pcntl are declared in composer suggest (not required, so SSH-only installs work without them) and the posix_kill calls are guarded so a missing extension degrades instead of fataling.
  • Schemas\Components\TerminalDashboard — toggle-driven terminals with auto-layout. A roster of named terminal sources (each a distinct connection), each opened or closed by a button; the open panes are auto-arranged by a count-to-preset map (->arrangement([2 => 'columns', 3 => 'main-left'], default: 'tiled'), capped by ->maxOpen(), up to 4). Closing a source destroys its pane (WebSocket + PTY). Backed by Livewire\StreamDashboard (Locked source roster; toggle() re-checks the useStreamTerminal gate on open and enforces the cap) and new layout presets in LayoutTree::arrange($paneIds, $preset) — tiled (2 columns; 3 = one tall left + two stacked right; 4 = even 2x2 grid), columns, rows, main-left, main-top, all with even ratios. Same flat-keyed-pane isolation as the workspace, so toggling one source never re-renders the others.
  • Schemas\Components\TerminalWorkspace — tmux-style dynamic tiling. One terminal that splits into arbitrarily nested panes at runtime: prefix-key shortcuts (default ctrl+b + %/"/x/z/arrows/hjkl, fully remappable via keymap()/config), drag-resizable dividers, keyboard resize, pane zoom (siblings stay live), directional focus navigation, a per-pane close (×) button shown whenever more than one pane exists (the sole remaining pane has none and fills the workspace), and an empty-state respawn. Backed by Livewire\StreamWorkspace (pane roster + binary split tree as Locked props; every mutation server-validated: useStreamTerminal gate, maxPanes ceiling, ratio clamping) and Data\Layout\LayoutTree (pure split/collapse/ratio operations, persistence-ready wire shape). New panes clone their split source (tmux semantics) or a defaultPane() template evaluated at schema build. Panes render as a flat keyed list of absolutely-positioned siblings with Alpine-bound geometry, so a live pane's canvas and WebSocket never re-render when a sibling splits or closes.
  • Themes — Themes\TerminalTheme + shipped presets. An extendable theme object controls terminal font (family, size — previously hardcoded in the view), colors (background, foreground, cursor, selection, ANSI palette), and pane divider styling (width, line style, color, focus highlight). Fluent partial overrides keep every other default: TerminalTheme::make()->fontSize(15)->dividerColor('#7aa2f7'). Ship a theme by subclassing (class BrandTheme extends TerminalTheme { protected string $background = '#0b1021'; }) — make() returns the subclass and it stays fluently tweakable. Presets Themes\TokyoNight and Themes\Dracula included. Apply via ->theme(TokyoNight::make()) on WebTerminalStream, TerminalWorkspace, or TerminalGrid (grid/workspace forward the terminal look to panes and emit divider CSS custom properties — --wts-divider-width/-style/-color/-focus, --wts-terminal-bg — on the container). A raw colors array still works for just the ghostty theme; theme() now accepts TerminalTheme|array|Closure, and StreamTerminal gained fontFamily/fontSize props.
  • Directional pane creation. New PaneAction::SplitLeft/SplitRight/SplitUp/SplitDown place the new pane on the named side of the focused one (LayoutTree::splitPane() and StreamWorkspace::splitPane() gained a $before flag; left/up insert the new pane before its sibling). Unbound in the default keymap — opt in, e.g. Keymap::default()->bind(PaneAction::SplitLeft, 'ctrl+arrowleft'). The keyboard close action now refuses to close the last remaining pane (the workspace always keeps one), matching the per-pane × button.
  • Enums\PaneAction and Data\Keymap — the configurable keyboard-shortcut model for the upcoming TerminalWorkspace tiling component. Keymap::tmux() ships the tmux preset (prefix ctrl+b, %/" splits, x close, z zoom, arrows/hjkl focus, ctrl+arrows resize); ->prefix(), ->bind(), ->unbind() customize it; Keymap::fromArray() hydrates the new workspace.shortcuts config section. New workspace config block: shortcuts, max_panes, min_pane_ratio, resize_step.
  • ConnectionConfig::toTransportArray() — the full wire-format array (credentials included) handed to the PTY bridge; toArray() stays credential-free for logging/display.
  • Schemas\Components\TerminalGrid — first increment of the tiling layer. Lays out N WebTerminalStream panes in a flush CSS grid: TerminalGrid::make()->panes([...]) with Filament-style responsive columns() (default 2), paneGap(int $px) (positive gaps render as pane dividers via the container background), height(string) with equally shared rows, auto-applied frameless() + squareCorners() per pane (a pane's explicit setting wins), grid-level connectionBehavior() forwarded to panes without their own, a CSS :focus-within focused-pane ring, and per-pane wire:key isolation. Manual composition of frameless terminals in your own Grid still works.
  • connectionBehavior() is now fully implemented instead of merely accepted. ConnectionBehavior::Manual renders a themed, centered Connect affordance and opens the WebSocket (and PTY) only on click — never-opened panes cost no server process and write no connection log rows. Auto auto-connects and adds a connect/disconnect toggle (header action, or floating overlay control when frameless()), with a Reconnect affordance after disconnect. The schema component default remains Always (auto-connect, no controls), so existing code is unaffected. Disconnect closes the WebSocket with a clean client close (code 1000).
  • Initial extraction of the Stream terminal from mwguerra/web-terminal (its feature/frameless branch) as a standalone, Stream-mode-only package.
  • Full interactive PTY over WebSocket (ReactPHP server + ghostty-web WASM terminal emulator), for local shells and SSH (password or key auth).
  • Filament integration: WebTerminalStream::make() schema component, WebTerminalStreamPlugin with a Terminal page and a Terminal Logs resource.
  • Connection lifecycle auditing: StreamTerminal dispatches TerminalConnectedEvent / TerminalDisconnectedEvent and records them to the terminal_stream_logs table via TerminalLogger.
  • Artisan commands: terminal-stream:install, terminal-stream:serve, terminal-stream:make-page, terminal-stream:logs:cleanup.
  • Docker-backed integration suite (composer test:integration): a throwaway alpine sshd container (tests/docker/, committed test-only keys) exercises real SSH password/key/passphrase auth, echo round-trips, remote PTY resize, and termination; a Linux php container covers the Linux-only local-PTY resize path; a full-stack test boots terminal-stream:serve and drives it with a real WebSocket client (token auth, Origin rejection, single-use token replay).
  • Playwright e2e suite (npm run test:e2e): scripts/e2e/setup.sh scaffolds a dedicated Laravel 13 + Filament 5 app into the gitignored tests/e2e-app/; specs assert per-pane WebSocket frame isolation, manual-connect laziness, workspace split/close/zoom/drag-resize/focus-navigation via real Ctrl+B chords, and that a live pane's socket survives sibling splits unblinked. GitHub Actions workflows run unit (PHP 8.3/8.4 × Laravel 12/13), integration, and e2e jobs.

Fixed

  • Disconnect logging was unreliable. The browser's disconnect() only fires on a clean teardown, so a killed tab or dropped network left a session showing as "connected" forever. The WebSocket server now records the disconnect on socket close — the one place every disconnect is observed — de-duplicated against a disconnect the browser may already have logged.
  • TerminalLogResource did not scope to the tenant. With multi-tenancy configured (logging.tenant_column + a resolver), the log list is now filtered to the resolved tenant via getEloquentQuery(), so one tenant can no longer read another's terminal history.
  • Multi-tenant migration and dead columns. The --with-tenant migration's tenant_id is now nullable + nullOnDelete (a resolver that legitimately returns null no longer silently drops the row), both migrations gained a standalone created_at index for the retention cleanup, and the always-empty command / output / exit-code / duration columns, filters, infolist sections, and widget stats were removed from the Logs UI (this package logs connection lifecycle only).
  • terminal-stream:logs:cleanup --days rejected a non-numeric value and, critically, a negative one — --days=-5 fed olderThan(-5) a date in the future and silently deleted every log entry.
  • terminal-stream:install --force wrote a second timestamped migration alongside an existing one, so migrate failed on a duplicate Schema::create(); it now overwrites the existing migration in place.
  • terminal-stream:make-page wrote the given name verbatim into the class declaration; an invalid name (spaces, dashes, leading digit) produced uncompilable PHP. It is now validated.
  • terminal-stream:serve bound to an empty host / port 0 when passed an empty --host= / --port=; these now fall back to config.
  • composer analyse was broken — it referenced a phpstan binary that was never a dependency. PHPStan (level 5 on src, with a baseline of pre-existing findings) is now wired up and green.
  • Packaging shipped development files. A .gitattributes export-ignore set now keeps the throwaway SSH test keys, the 14k-file e2e app, docs, and CI tooling out of the distributed package.
  • Workspace divider drag was broken. The drag geometry used Alpine's $el magic inside the divider's pointermove handler, where $el is the 8px divider element rather than the workspace container — so the pointer-to-ratio math divided by 8px instead of the workspace width and the split ratio oscillated between its min/max clamps instead of tracking the cursor. The component now captures the workspace root once at init and uses it for all geometry. Divider dragging is now smooth and tracks the pointer.
  • Live resize of SSH terminals was a silent no-op. phpseclib's setWindowSize() only stores dimensions for the pty-req sent when the shell channel opens — calling it on an open shell never reaches the remote PTY. WebSocket\InteractiveSsh (an SSH2 subclass) now sends the RFC 4254 §6.7 window-change channel request, so browser resizes propagate to remote shells. Proven by an integration test against a real sshd container.
  • TerminalBuilder silently dropped SSH credentials when given a ConnectionConfig value object (the old withConfig() went through the credential-stripping toArray()), so SSH terminals built that way could never authenticate. ->connection($config) now routes through toTransportArray().
  • WebTerminalStream::ssh() named its first parameter $config, so the documented named-parameter form (->ssh(host: '…', username: '…')) failed with "Unknown named parameter". The parameter is now $host; positional array/Closure usage is unchanged.

Changed

  • The bundled JavaScript is now registered with the namespaced Filament asset id web-terminal-stream (was stream-terminal) so co-installing mwguerra/web-terminal cannot collide (Filament keys scripts globally by id). After upgrading, run php artisan filament:assets so the browser loads the asset at its new published path — otherwise the old file 404s and the terminal fails to boot.

  • Breaking — pre-1.0 fluent API clean break. One connection vocabulary for both the schema component and TerminalBuilder (shared ConfiguresConnection trait): ssh() with named args is canonical (privateKey: replaces key:; port defaults to null → 22), local(?workingDirectory, environment) gains parameters, workingDirectory() exists on both surfaces; sshWithPassword()/sshWithKey()/withConfig()/connection(type, array) were removed. streamTheme() → theme(). ConnectionBehavior::AutoWithButton/AutoHidden → Auto ('auto')/Always ('always'); getEffectiveConnectionBehavior() → non-null getConnectionBehavior(); toFlags() removed. Deprecated windowControls()/startConnected()/autoConnect() aliases, the EmitsDeprecationNotices machinery, and the deprecations config block were deleted. StreamTerminal lost the redundant showWindowControls/autoConnect props (derived from chrome/connectionBehavior) and its streamTheme prop is now theme. log() is named-args only. Default height unified at 400px (fluent builders previously said 350px). TerminalGrid::terminals() → panes(), gap(int) → paneGap(int) (Filament's boolean gap() is no longer overridden). Plugin only() removed; withoutTerminalPage()/withoutTerminalLogs() now subtract from the components() whitelist instead of being ignored. See UPGRADING.md §4b for the full old → new table.

  • filament/filament (^5.0) added to require-dev (dev-only — the production suggest is unchanged) so the Filament-dependent tests (schema components, plugin) run for real instead of self-skipping. The previously skipped plugin tests were rewritten against the actual plugin API; the skipping had hidden stale assertions of Classic-era methods (isEnabled(), disabled(), allowedCommands()) that never existed in this package. composer.lock is now committed.

  • The WebSocket server's ReactPHP stack (react/socket, react/event-loop, ratchet/rfc6455) moved from require-dev/suggest to require — it is the package's core and now installs automatically.

  • Breaking (vs. mwguerra/web-terminal): Classic command-by-command mode was removed entirely — Stream is the only rendering path. The command whitelist/sanitizer/rate-limit security layer, interactive TUI sessions, mode toggles (mode(), dual(), streamTerminal(), classicTerminal()), and the related fluent knobs and config keys do not exist in this package.

  • Package renamed to mwguerra/web-terminal-stream with namespace MWGuerra\WebTerminalStream\, config file config/web-terminal-stream.php, view/translation namespace web-terminal-stream::, terminal-stream:* Artisan commands, terminal-stream/ws-token route, and terminal_stream_logs table, so it can be installed side-by-side with mwguerra/web-terminal.

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.
adachsoft/changelog-linter
adachsoft/collection
payerurl/telegrambot-crypto-payment-instant-payout
dskripchenko/laravel-hybrid-search
riorizkyrainey/lara-ced
mehedimi/static-vel
zharp/zharp
nonsapiens/bigquery-model-sync
lgu-bayambang-ict-office/lgu-laravel-development-standards
andydefer/laravel-support
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
adachsoft/php-code-style