---
title: "Shell"
canonical_url: https://developer.repebble.com/pebbleos/docs/subsystems/shell/
last_updated: 2026-10-21
sdk_version: 4.33.1
---
# Shell

Source: https://developer.repebble.com/pebbleos/docs/subsystems/shell/

`subsys/shell/` is the debug command shell. Its public API is `include/pbl/shell/shell.h`, and backends implement `include/pbl/shell/backend.h`. Commands are defined next to the code they drive, collected at link time, and served by any number of shell instances, one per backend.

## Commands

A command is a `struct pbl_shell_cmd`: a name, a help string, a handler and optionally a table of subcommands, so commands form a tree (`bt disc start`).

- `PBL_SHELL_CMD_REGISTER()` / `PBL_SHELL_CMD_ARG_REGISTER()` register a root command from any file. The entries land in the `.pbl_shell_root_cmds` linker section, sorted by name.

- A file-local subcommand table is a plain `struct pbl_shell_cmd` array of `PBL_SHELL_CMD()` entries ending with `PBL_SHELL_SUBCMD_SET_END`.

- `PBL_SHELL_SUBCMD_SET_CREATE()` builds a subcommand set that other files extend with `PBL_SHELL_SUBCMD_ADD()`, without referencing any symbol of the owner. The entries live in `.pbl_shell_subcmds.<set>.<name>` input sections, sorted between a start marker and a terminator, so a set is a contiguous, NULL-terminated array once linked. This is how, for instance, the Bluetooth, console and driver code all add to `bt` or `flash`.

Handlers get `argc`/`argv` with `argv[0]` being the matched command name. `mandatory` (which counts `argv[0]`) and `optional` let the core check the argument count; `-h`/`--help`, unknown subcommands and commands without a handler print the help of the matched node. A handler returns 0 or a negative errno; `-EINPROGRESS` keeps the shell busy until `pbl_shell_cmd_done()`, for commands that finish from a callback.

Shell code is wrapped in `#ifdef CONFIG_SHELL`, and modules with a group of commands can gate them further; for example `drivers/imu/shell.c` builds with `CONFIG_IMU_SHELL`.

```
static int prv\_accel\_read(const struct pbl\_shell \*sh, size\_t argc, char \*\*argv) {...pbl\_shell\_print(sh, "x=%d y=%d z=%d mg", sample.x, sample.y, sample.z);return 0;}static const struct pbl\_shell\_cmd sub\_accel[] = {PBL\_SHELL\_CMD(read, NULL, "Read one sample", prv\_accel\_read),PBL\_SHELL\_SUBCMD\_SET\_END,};PBL\_SHELL\_CMD\_REGISTER(accel, sub\_accel, "Accelerometer", NULL);
```

## Instances and backends

`PBL_SHELL_DEFINE()` creates an instance bound to a backend. Every instance has its own line buffer and state, and commands receive the instance they run on, so their output goes back where the command came from. Commands of every instance run on KernelBG.

- **Interactive** instances (with a prompt) feed received characters with `pbl_shell_input_from_isr()`. The core does line editing, echo, the prompt and tab completion of command names on KernelBG.

- **Line** instances (no prompt) submit whole command lines with `pbl_shell_execute_line()`, and get a `done` callback when the command finishes.

The firmware has two backends, both in `fw/console/`:

- `shell_dbgserial.c`: interactive, on the debug serial. Ctrl-C switches the serial port from logs to the shell, Ctrl-D back to logs.

- `shell_pulse.c`: line-based, on the PULSE prompt protocol, which is what `pbl console` talks to. Each output line is one prompt message.

Generated from PebbleOS [3a7b71785](https://github.com/coredevices/pebbleos/commit/3a7b71785fd439666c4a8bc116f561140e8dec01). This page is maintained in the pebbleos repository: [docs/subsystems/shell.md](https://github.com/coredevices/pebbleos/blob/3a7b71785fd439666c4a8bc116f561140e8dec01/docs/subsystems/shell.md).
