Skip to content

Sesh Console

Sesh is the command-line interface to your application. Inside a project, run it through the maat shim or directly:

Terminal window
maat list
maat make:controller PostController --api
dart run bin/maat.dart list

Running Sesh with no arguments lists every available command with its description.

Available Commands

Command Description
serve Run the development server with file watching.
route:list Show every registered route.
channel:list Show every registered channel pattern.
key:generate Generate and store the application key.
about Show application name, environment, debug state, Dart and Maat versions, and base path.
make:controller Create a controller. --api (or --resource) scaffolds the five resource actions; --invokable scaffolds a single-action controller.
make:middleware Create a middleware class.
make:request Create a form request.
make:provider Create a service provider.
make:command Create a console command.
make:event Create a dispatchable event.
make:channel Create a channel authorization stub.
make:listener Create an event listener. --event=PostPublished types and imports the event.
make:mail Create a mailable under lib/app/mail.
make:notification Create a notification under lib/app/notifications.
tailwind Compile the application CSS with Tailwind. --watch, --minify, --input=, --output=.
thoth:start Start the Thoth WebSocket server. --host=, --port=.
thoth:ping Check the Thoth health endpoint. --host=, --port=.

tailwind comes from khnum_maat and is only present when a project depends on it — the standard preset does, --api does not. See Frontend.

make:mail comes from amarna. The standard and --api presets both register it because either kind of application may send mail.

make:notification comes from sistrum and is registered by both presets for the same reason.

thoth:start and thoth:ping come from thoth_realtime. Register them in the console kernel when the application provides realtime WebSockets. See WebSockets with Thoth.

Generating Application Code

Every generator accepts --force to overwrite an existing file, and supports nested names:

Terminal window
maat make:controller Admin/UserController --api
# lib/app/http/controllers/admin/user_controller.dart, class UserController

Without --force, a generator refuses to overwrite and exits with a non-zero status, so it will not silently destroy work.

Generated files land already formatted: after writing a file, a generator runs dart format over it, so the output matches what your editor and CI would produce and never shows up as noise in the next diff. If dart format cannot be run, the generator says so and keeps the file — the code is valid Dart either way, just untidy.

Get help for any command:

Terminal window
maat help make:controller

Writing Commands

class SendEmails extends Command {
@override
String get name => 'mail:send';
@override
String get description => 'Send the queued mail';
@override
String get signature => '{user} {--queue=default} {--D|dry-run}';
@override
Future<int> handle() async {
final user = argument('user');
final queue = option('queue');
if (flag('dry-run')) {
warn('Dry run — nothing will be sent.');
return 0;
}
info('Sending mail for $user on $queue.');
return 0;
}
}

Generate the skeleton with:

Terminal window
maat make:command SendEmails

Register it in lib/app/console/kernel.dart, and it appears in maat list alongside the built-ins.

The return value of handle is the process exit code. Return 0 for success and a non-zero value for failure, so scripts and CI can react.

Signature Syntax

Form Meaning
{name} Required argument.
{name?} Optional argument.
{name=default} Optional argument with a default.
{--flag} Boolean flag, false unless passed.
{--opt=} Option that takes a value.
{--opt=default} Option with a default value.
{--o|opt} Short alias, so -o works too.

Read them back with argument(name), option(name) and flag(name). Anything left over is available as rest.

The signature parser does not reject malformed tokens. A typo such as a missing closing brace produces an argument with a garbled name, and the matching option() or flag() call then quietly returns nothing. If a new command’s options do not seem to arrive, check the signature string first.

Bad arguments print the command’s usage and exit with status 1.

Writing Output

Method Goes to Notes
info(msg) stdout
line(msg) stdout
warn(msg) stdout Prefixed with WARNING:.
error(msg) stderr
table(headers, rows) stdout Columns are aligned to their widest cell.

Always write through these rather than print. They target the sinks Sesh injects, which is what makes commands testable:

final out = StringBuffer();
final maat = Sesh(app, out: out, commands: [SendEmails()]);
expect(await maat.run(['mail:send', 'ada']), 0);
expect(out.toString(), contains('Sending mail for ada'));

Keep rows the same width as your headers — extra cells in a row are dropped.

Accessing the Application

Every command has app, the running application, so the container and configuration are available:

@override
Future<int> handle() async {
final repo = this.app.make<PostRepository>();
line('Posts: ${repo.count()}');
return 0;
}