Laravel package · PsySH + artisan

lara-shell — an interactive artisan command shell

A PsySH-based REPL where bare words run as artisan commands — with managed background jobs, aliases, macros, fuzzy matching and a production safety guard. Raw PHP still evaluates, so it is Tinker and your command runner fused into one prompt.

php ^8.2 laravel 10 · 11 · 12 psy/psysh ^0.12 MIT
$ composer require amims71/lara-shell

Then launch the prompt with php artisan shell (aliases: terminal, repl).

The idea

One prompt, three languages

Run php artisan shell and you get an artisan> prompt. Every line you type is classified and routed automatically — you never prefix an artisan command.

  • Bare words run as artisan. Type migrate or route:list and it dispatches the artisan command directly — no php artisan boilerplate.
  • Raw PHP still evaluates. Anything that is not a recognized command falls through to PsySH and is evaluated as PHP — the full Tinker experience, in the same session.
  • Escape to PHP with ;. Prefix a line with a semicolon to force PHP evaluation, even when the first word collides with a command name.
  • Fuzzy resolution. Prefix abbreviations (mig), colon-segment abbreviations (m:fmigrate:fresh) and fzf-style subsequence matching all resolve to the canonical command when the winner is unambiguous.

Under the hood each typed line is classified as artisan, macro, meta or php — empty lines and anything starting with ; go to PHP; a leading @ runs a macro; a first word that is a built-in meta-command runs that; a resolvable artisan name dispatches; everything else is PHP.

Capabilities

Command reference

Every capability, with the exact syntax read from the package source. The prompt is artisan>.

Bare-word artisan dispatch

Type any artisan command with no prefix. Arguments and options pass straight through to the underlying command.

artisan> route:list --json

Fuzzy matching resolve

Prefix, colon-segment and subsequence matching resolve short input to the real command name when a single clear winner exists.

artisan> m:f # → migrate:fresh

Palette & ? search

Search the command catalog by name or description. An empty query lists everything, name-sorted. ? is the shortcut for palette.

artisan> ? queue

PHP eval + ; escape tinker

Unrecognized lines evaluate as PHP. Lead with a semicolon to force PHP even when the first token looks like a command.

artisan> ; User::count()

Background jobs & · jobs

Append & to background a command. Long-running commands (serve, queue:work, …) background automatically. jobs lists them across all terminals.

artisan> queue:work --tries=3 & artisan> jobs

kill & logs jobs

kill <id> terminates a job and its whole process tree. logs <id> prints the captured output; -f / --follow tails it live.

artisan> logs 9f3a1c2b -f artisan> kill 9f3a1c2b

Aliases alias

Aliases rewrite the first word of a line. Add, remove and list them; a real command always wins over an alias of the same name.

artisan> alias add fresh migrate:fresh --seed artisan> alias list

Macros @

A macro (defined in .lara-shell.php) is a named list of steps run in order. Invoke it with the @ sigil; macros may call other macros.

artisan> @reset

Safety guard guard

In guarded environments (default: production) destructive commands require you to re-type the command name to confirm; a block list can forbid them outright. --force/-f also triggers confirmation.

artisan> migrate:fresh This command is guarded. Re-type "migrate:fresh":

reload catalog

Reload application code and refresh the command catalog so newly added commands and aliases are resolvable without leaving the shell.

artisan> reload

Meta-command syntax

CommandDescriptionExample
jobs List background jobs (ID, command, status, uptime) across all terminals. jobs
kill <id> Kill a background job and its entire process tree (SIGTERM, then SIGKILL survivors). kill 9f3a1c2b
logs <id> [-f] Print a job's log; -f/--follow tails it until the job stops. logs 9f3a1c2b --follow
help / h Show the built-in guide to everything the shell can do. Aliases: h, about, guide. help
help <command> Show usage, arguments and options for a command, from the live catalog. help migrate
reload Reload code and refresh the command catalog. reload
alias add <name> <expansion> Create/replace an alias whose expansion replaces the first word. alias add fresh migrate:fresh --seed
alias rm <name> Remove an alias. alias rm fresh
alias list List all aliases (the default when alias is run with no subcommand). alias list
palette [query] Fuzzy-search the command catalog; empty query lists everything. Aliased to ?. palette queue
? [query] Shorthand for palette. ? migrate
@<macro> Run a named macro's steps in order (defined in .lara-shell.php). @reset
<cmd> & Trailing & backgrounds the command; long-running commands background automatically. serve &
; <php> Leading ; forces the line to evaluate as PHP. ; App\Models\User::count()

A realistic session

What it feels like

Start a dev server in the background, migrate, define a shortcut, run a macro, drop into PHP, then manage jobs — all without leaving the prompt.

php artisan shell
# long-running commands auto-background — no & needed
artisan> serve
Started background job 1a2b3c4d (serve)

artisan> migrate
This command is guarded. Re-type "migrate" to proceed: migrate
   INFO  Running migrations.
  2024_01_01_000000_create_users_table ..................... DONE

# make a reusable shortcut (rewrites the first word)
artisan> alias add fresh migrate:fresh --seed
Added alias fresh => migrate:fresh --seed

# run a macro defined in .lara-shell.php (steps run in order)
artisan> @reset
  cache:clear ... DONE
  config:clear ... DONE
  migrate:fresh --seed ... DONE

# unrecognized input evaluates as PHP — full Tinker
artisan> ; App\Models\User::count()
=> 42

# background a worker explicitly with a trailing &
artisan> queue:work --tries=3 &
Started background job 9f3a1c2b (queue:work --tries=3)

artisan> jobs
+-----------+----------------------+---------+--------+
| ID        | Command              | Status  | Uptime |
+-----------+----------------------+---------+--------+
| 1a2b3c4d  | serve                | running | 3m12s  |
| 9f3a1c2b  | queue:work --tries=3 | running | 8s     |
+-----------+----------------------+---------+--------+

artisan> logs 9f3a1c2b -f
[2024-01-01 12:00:08] Processing: App\Jobs\SendWelcome
# ^C to stop following, then reap the job
artisan> kill 9f3a1c2b
Killed job 9f3a1c2b.

Configuration

Two config files

Package behavior lives in config/lara-shell.php. Your project-local aliases and macros live in .lara-shell.php at the project root — created and updated for you as you run alias add/alias rm.

Publish the package config with php artisan vendor:publish --tag=lara-shell-config.

config/lara-shell.phppackage
return [
  // command name + aliases
  'command' => [
    'name' => 'shell',
    'aliases' => ['terminal', 'repl'],
  ],

  // auto-backgrounded commands
  'long_running' => [
    'serve', 'queue:work', 'queue:listen',
    'schedule:work', 'horizon', 'pail',
    'reverb:start', 'octane:start', ...
  ],

  // production safety guard
  'guard' => [
    'environments' => ['production'],
    'block'   => [],
    'confirm' => [
      'migrate', 'migrate:fresh',
      'db:wipe', 'db:seed',
      'cache:clear', 'optimize:clear', ...
    ],
  ],

  'php_escape'  => ';',   // force-PHP prefix
  'macro_sigil' => '@',   // macro trigger
];
.lara-shell.phpper project
// project root — aliases & macros.
// managed by `alias add` / `alias rm`,
// and safe to hand-edit.
return [

  // first-word rewrites
  'aliases' => [
    'fresh' => 'migrate:fresh --seed',
    'tw'    => 'tinker',
  ],

  // named step lists, run in order.
  // invoke with @name; steps may
  // reference other @macros.
  'macros' => [
    'reset' => [
      'cache:clear',
      'config:clear',
      'migrate:fresh --seed',
    ],
    'up' => [
      '@reset',
      'serve',
    ],
  ],
];
Guard behavior: the guard only engages when the app is running in a guarded environment (default production). Commands on the confirm list — or any command carrying --force / -f — prompt you to re-type the command name; commands on the block list are refused outright. Elsewhere (local, staging) everything runs unguarded.