Custom commands do not have automatic triggers: you must 'dispatch' them.
From the intro in the docs:
Commands are the communication system used to wire everything together in Lexical. Custom commands can be created using createCommand() and dispatched to an editor using editor.dispatchCommand(command, payload). Lexical dispatches commands internally when key presses are triggered and when other important signals occur. Commands can also be handled using editor.registerCommand(handler, priority), and incoming commands are propagated through all handlers by priority until a handler stops the propagation (in a similar way to event propagation in the browser).
The built-in commands are dispatched in the same way.
If you look at the LexicalEvents.ts file (referenced frequently in the docs), you can see in the function onKeyDown how their keyboard-triggered commands are implemented:
function onKeyDown(event: KeyboardEvent, editor: LexicalEditor): void {
...
const {keyCode, shiftKey, ctrlKey, metaKey, altKey} = event;
if (isMoveForward(keyCode, ctrlKey, altKey, metaKey)) {
dispatchCommand(editor, KEY_ARROW_RIGHT_COMMAND, event);
} else if (isMoveToEnd(keyCode, ctrlKey, shiftKey, altKey, metaKey)) {
dispatchCommand(editor, MOVE_TO_END, event);
} ...
It uses helper functions to check the key and some of the modifier keys to determine if a certain combo was used, and, if so, dispatches the relevant command.
The pattern of including the original event is noted in the docs for dispatchCommand:
The payloads are typed via the createCommand(...) API, but they're usually a DOM event for commands dispatched from an event listener.
Note: if you use Ctrl+S, you'll need to use preventDefault() to prevent the browser trying to download the page.