farsi/nova-command-center
Adds a command center to Laravel Nova: run Artisan commands and quick actions from one panel without opening a terminal. Browse available commands, execute them with arguments and options, and review output right inside your admin UI - handy for routine maintenance and debugging.
Install and register
composer require farsi/nova-command-center
Register the tool in your NovaServiceProvider (Nova 4 uses the tools() method; Nova 5 still honors it, or use Nova::tools([...]) in boot() — match whatever your app already does for other tools):
public function tools()
{
return [
new \Farsi\NovaCommandCenter\NovaCommandCenter,
];
}
Then clear Nova's caches so the new menu entry appears:
php artisan nova:publish --force # only if you need assets
php artisan config:clear
php artisan route:clear
Authorize before you browse
A command runner is a root-adjacent surface. Before you open it, confirm:
Gate::before for a super-admin, or a dedicated policy/canSee on the tool).php artisan vendor:publish --tag=nova-command-center-config, or check the README for the tag) exposes a whitelist of permitted commands. Start with an empty/short list, not the default.First use case
The 80% case is deploy-adjacent housekeeping that currently costs you an SSH session: php artisan optimize:clear, php artisan queue:restart, php artisan migrate --force, php artisan storage:link. Get one of those working end-to-end (run it, read the output, confirm exit status) before adding more.
Where to look first
NovaServiceProvider: your only integration point in application code.vendor/farsi/nova-command-center/routes and src/Http): tells you what middleware protects the endpoint and whether it's web + Nova auth or something custom.Treat the allow-list as the primary API. The productive workflow is to curate a named set of commands per project — "cache", "queue", "migrate", "diagnostics" — rather than exposing a generic prompt. Group them so an on-call engineer sees exactly five buttons, each with a known blast radius:
php artisan about, php artisan queue:failed, df -h, php artisan migrate:statusoptimize:clear, view:clear, queue:restart, schedule:runmigrate --force, migrate:rollback, cache:clear in productionGate by environment and role, not just by Nova access. Wrap the tool in a canSee() check so it's absent on local (where you have a terminal 30cm away) and present only for the ops role on staging/production:
public function tools()
{
return [
(new \Farsi\NovaCommandCenter\NovaCommandCenter)
->canSee(fn ($request) => $request->user()->hasRole('ops')),
];
}
(Verify canSee chaining against the package's tool class; if it isn't exposed, do the check in Nova::tools() or via a gate.)
Use it as a break-glass console, not a CI replacement. The healthy pattern: deploys and migrations still happen in your pipeline; the Nova tool exists for the 2am "queue is stuck, restart it" and "cache is poisoned, clear it" moments. If you find yourself clicking the same command daily, that's a scheduled task or a deploy step begging to be written.
Pair every mutating command with a read that proves it worked. Make queue:restart adjacent to a queue:monitor-style check, migrate adjacent to migrate:status. On a UI with no terminal scrollback, you want the verification one click away.
Diagnostics over administration. Shell commands are most valuable here for things Artisan can't do: tail -n 100 storage/logs/laravel.log, du -sh storage/*, ps aux | grep queue, free -m. These are read-only, low-risk, and save the most time.
Multi-server awareness. If you run more than one app node, remember the tool runs on whichever node answers the request — so queue:restart clears one worker pool, not all of them. For anything cluster-wide, you still want a real orchestration path (Envoyer, Forge, Horizon's own UI).
Audit trail. If the package doesn't ship one, log invocations yourself — a model observer, a middleware, or a Log::channel('commands') line capturing user, command, timestamp, exit code. In an incident review, "who ran migrate:rollback at 02:14" is the first question asked.
The PHP binary is the classic failure. Your web request runs under PHP-FPM with a different PATH and a different php.ini than your SSH shell. php artisan ... may resolve to nothing, or to a different PHP version with different extensions. Prefer PHP_BINARY over bare php, and verify by running php -v through the tool once and comparing to your terminal. Same story for composer, node, and anything installed via a version manager.
File ownership drift. Commands run as the web user (www-data, nginx, forge). A shell command that writes to storage/ or bootstrap/cache/ will leave root- or web-user-owned files that later break a deploy running as a different user. After using it, sanity-check with ls -l storage/framework if deploys start failing on permissions. Conversely, commands your deploy user needs to write should not be run here.
Timeouts and buffering. Long-running commands (migrate on a big table, queue:work) will hit max_execution_time, PHP-FPM's request_terminate_timeout, or your load balancer's idle timeout — often after the command has started mutating state, leaving you with a truncated output and no exit code. Keep migrations in the deploy pipeline, and never run a daemon (queue:work, horizon) synchronously from a web request; use queue:restart to bounce an existing supervisor instead.
No interactivity. Anything that prompts (migrate in production without --force, db:wipe, confirmation prompts) will hang or fail. Always pass --no-interaction / --force explicitly in the configured command string, which also makes the intent visible in the UI.
Config caching bites the allow-list. If you've run php artisan config:cache in production, editing the whitelist on the server does nothing until you re-cache. This is a feature (it prevents a compromised admin session from adding commands) but a confusing five minutes if you forget.
Output is not a log. Don't rely on the UI to retain output — page reloads and session timeouts lose it, and large outputs (migrate on 200 migrations, artisan route:list) can blow memory or get truncated. > storage/logs/x.log 2>&1 in the command string, then read the file, when output matters.
Destructive commands deserve a second gate. migrate:fresh, db:wipe, cache:clear on a Redis cluster, storage:link over an existing symlink — these are one misclick from an outage. Keep them out of the tool entirely, or isolate them behind a separate tool instance visible only to a super-admin role. Nova's own action-confirmation modals are worth mimicking in your UI expectations; don't assume the package provides one.
Keep the package updated, and read diffs. An 8-star package with a command-execution surface is a small trust boundary in your stack. Pin it, review changelogs before upgrading (especially anything touching middleware or auth), and confirm the release notes for Nova 5 compatibility — the version matrix matters here since Nova 4→5 changed tool registration and asset loading.
Extension point: wrap, don't fork. If you need custom commands with parameters, richer output formatting, or per-command permissions, the cleanest path is usually a thin first-party Nova tool that shells out via Symfony's Process component for the few cases the package doesn't cover — rather than patching vendor code that will be overwritten on the next composer update.
How can I help you explore Laravel packages today?