Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/metadata.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@
- Description: A framework for full-screen terminal applications with split panes, focus, scoped keybindings, and a status bar.
- Homepage: `https://github.com/relay-cli/relay-shell#readme`
- Topics: `typescript`, `react`, `ink`, `terminal-ui`, `tui`, `cli`, `keybindings`, `developer-tools`
- Social preview: `.github/social-preview.png`
Binary file added .github/social-preview.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@

- Added the full-screen `ShellFrame`, the `runShell` renderer, Relay themes, and the `relay-shell` demonstration command.
- Added layered keybindings with `KeymapProvider`, `useKeybindings`, key specs, `HelpOverlay`, and `KeyHints`.
- Added `Split` and `Pane` layouts with fixed, percentage, and shared sizes, `Tab` focus movement, and keys that follow the focused pane.
- Added `StatusBar` with segments published by components, transient notices, and `StatusStore` for status from outside React.
- Added `Shell`, which assembles the frame, panes, status bar, key hints, help, and quitting, and rebuilt the demonstration on it.
86 changes: 60 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# Relay Shell
<p align="center">
<img src="./docs/assets/banner.png" alt="Relay Shell" width="100%">
</p>

Relay Shell is a framework for full-screen terminal applications. It gives an Ink application the parts every terminal tool needs before it can show anything useful: a frame that fills the screen, split panes with focus, keybindings that follow the focused pane, and a status bar.
Relay Shell is a framework for full-screen terminal applications. It provides the frame every terminal tool needs before it can show anything useful: a screen-filling layout, split panes with focus, keybindings that follow the focused pane, a keyboard reference, and a status bar.

It is built on Ink and React. An application supplies the content of each pane; Relay Shell decides where it goes, which pane receives each key, and what the status bar says.

## Try the demo

Expand All @@ -9,57 +13,87 @@ npm install
npm run demo
```

Press `?` for the keyboard reference and `q` to quit. To print one frame without opening the interactive screen:
The demo opens a small host inventory with a list, a detail pane, and an events pane. It uses fixed sample data and does not contact any service. Press `Tab` to move between panes, `j` and `k` to move through hosts, `?` for the keyboard reference, and `q` to quit.

To print one frame and exit without opening the interactive screen:

```sh
npx tsx src/cli.tsx --demo --snapshot
```

## Use it in an application
## Controls

These keys come with every `Shell`. Applications add their own on top.

| Key | Action |
| ------------- | ----------------------------- |
| `Tab` | Focus the next pane |
| `Shift+Tab` | Focus the previous pane |
| `?` | Toggle the keyboard reference |
| `Esc` | Close the keyboard reference |
| `q`, `Ctrl+C` | Quit |

## Use the package

`Shell` assembles the whole frame. Children are usually a `Split` of `Pane`s, and `runShell` renders on the alternate screen so the terminal's scrollback is restored on exit.

```tsx
import {Text} from 'ink';
import {ShellFrame, runShell} from '@relay-cli/shell';
import {Pane, Shell, Split, runShell, usePaneKeybindings} from '@relay-cli/shell';

const HostList = () => {
usePaneKeybindings([{keys: ['j', 'down'], description: 'Next host', run: selectNext}]);
return <Text>api-1</Text>;
};

await runShell(
<ShellFrame title="Inventory" subtitle="3 hosts">
<Text>Hello from a full-screen application.</Text>
</ShellFrame>,
<Shell title="Inventory" subtitle="3 hosts">
<Split direction="row">
<Pane id="hosts" title="Hosts" size={28}>
<HostList />
</Pane>
<Pane id="detail" title="Detail">
<Text>Select a host.</Text>
</Pane>
</Split>
</Shell>,
);
```

`runShell` renders on the alternate screen, so the terminal's scrollback is restored when the application exits.
Every part of `Shell` is also exported on its own, so an application that only needs keybindings or a status bar can adopt just that piece.

## Panes

`Split` and `Pane` divide the screen into regions. A size can be a number of cells, a percentage, or a share of the remaining space, and splits nest. The focused pane draws its border in the focus color, and `usePaneKeybindings` registers keys that only that pane receives.

## Keybindings

Components declare the keys they handle, and Relay Shell routes each key press to the right one. Overlays sit above panes, and panes sit above application-wide keys.
Components declare the keys they handle with `useKeybindings`, and Relay Shell routes each key press to exactly one of them. Overlays sit above panes and panes sit above application-wide keys, so the same key can mean different things in different panes. The keyboard reference and the hints row are generated from the same declarations.

```tsx
import {useApp} from 'ink';
import {KeyHints, KeymapProvider, ShellFrame, runShell, useKeybindings} from '@relay-cli/shell';
## Status bar

const App = () => {
const {exit} = useApp();
useKeybindings([{keys: ['q', 'ctrl+c'], description: 'Quit', run: () => exit()}]);
return <ShellFrame title="Inventory" footer={<KeyHints />} />;
};
Components publish short segments to the status bar with `useStatusSegment`, and any component can show a notice such as "Saved" with `useNotify`. A notice replaces the left side briefly, and then the segments return.

await runShell(
<KeymapProvider>
<App />
</KeymapProvider>,
);
```
## Documentation

- [Package API](./docs/api.md)
- [Architecture](./docs/architecture.md)
- [Panes and focus](./docs/panes.md)
- [Keybindings](./docs/keyboard.md)
- [Status bar](./docs/status-bar.md)

See [the keybinding guide](./docs/keyboard.md) for key specs, layers, and text input.
## Scope

This release covers the application frame: layout, focus, keybindings, help, and the status bar. Tables, charts, forms, and log views belong in the packages that build on it, such as Relay Dash.

## Development

```sh
npm install
npm run check
```

Relay Shell requires Node.js 22 or newer. See [the architecture notes](./docs/architecture.md) and [CONTRIBUTING.md](./CONTRIBUTING.md).
`npm run check` verifies formatting, linting, strict types, tests, and the distributable package. Relay Shell requires Node.js 22 or newer.

## License

Expand Down
39 changes: 39 additions & 0 deletions docs/api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# API

Everything is exported from `@relay-cli/shell`.

## Application

| Export | Purpose |
| ---------- | ---------------------------------------------------------------------------------------- |
| `Shell` | The complete frame: keybindings, pane focus, status bar, hints, help on `?`, quit on `q` |
| `runShell` | Render on the alternate screen and resolve when the application exits |

### `Shell` props

| Prop | Default | Meaning |
| ----------------- | ------------------ | ----------------------------------------------------- |
| `title` | required | Shown at the top left |
| `subtitle` | | Shown after the title |
| `theme` | `relayTheme` | Colors for every part of the shell |
| `statusLabel` | focused pane id | A string, or a function of the focused pane id |
| `statusRight` | | Content at the right end of the status bar |
| `initialFocus` | first pane mounted | The pane focused first |
| `quitKeys` | `['q', 'ctrl+c']` | Keys that quit, or `false` |
| `onQuit` | exit Ink | Called on a quit key |
| `helpKey` | `'?'` | Opens the keyboard reference, or `false` |
| `hints` | `true` | Show one line of key hints under the status bar |
| `interactive` | `true` | Read the keyboard; disable it to print a single frame |
| `width`, `height` | terminal size | Fixed size, mainly for tests |

## Building blocks

`Shell` is assembled from parts that can also be used on their own.

| Area | Exports | Guide |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------- |
| Frame | `ShellFrame` | [architecture](./architecture.md) |
| Keybindings | `KeymapProvider`, `useKeybindings`, `useKeyHints`, `KeyHints`, `HelpOverlay`, `KeyRegistry`, `keyLayers`, `parseKeySpec`, `formatKeySpec` | [keybindings](./keyboard.md) |
| Panes | `PaneFocusProvider`, `Split`, `Pane`, `usePane`, `usePaneKeybindings`, `useFocusedPane`, `usePaneFocus` | [panes](./panes.md) |
| Status bar | `StatusProvider`, `StatusBar`, `useStatusSegment`, `useNotify`, `StatusStore` | [status bar](./status-bar.md) |
| Themes | `relayTheme`, `monoTheme`, `shellThemes`, `ShellTheme` | |
5 changes: 4 additions & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,8 @@ Relay Shell is the frame a full-screen terminal application lives in. It does no
1. `runShell` renders an application on the alternate screen and resolves when it exits.
2. `ShellFrame` sizes the application to the terminal and reserves a title row and a footer.
3. `KeymapProvider` reads the keyboard once and asks a `KeyRegistry` which binding should receive each key. See [keybindings](./keyboard.md).
4. `Split` and `Pane` divide the body into regions, and `PaneFocusProvider` decides which pane receives pane-layer keys. See [panes](./panes.md).
5. `StatusProvider` collects segments and notices from any component, and `StatusBar` draws them in the footer. See [the status bar](./status-bar.md).
6. `Shell` nests those providers in the right order and draws the frame, status bar, hints, and help. See [the API reference](./api.md).

Layout, focus, and the status bar are added as separate modules so an application can use only the parts it needs.
Each provider can be used on its own; an application that only needs keybindings does not have to adopt panes.
22 changes: 22 additions & 0 deletions docs/assets/ARTWORK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Relay Shell artwork

The repository banner and social preview follow the direction of the other Relay repositories: the official Relay mark and product name on the left, and the real interface on the right.

## Banner

`banner.png` uses a 3:1 composition (2172 × 724). The left side contains the Relay mark, the product name, and a direct description. The right side depicts the actual demonstration application: a title row, a focused host list, a detail pane, an events pane, the status bar with its label and segments, and the key hints row.

## Social preview

`.github/social-preview.png` recomposes the same interface for a 2:1 thumbnail (1774 × 887). The description is removed and the product name is centered under the mark so it stays readable in repository cards.

## Sources

Both images are drawn from HTML in `source/` with the shared Relay palette and a monospace interface font, then captured with a headless browser:

```sh
chrome --headless=new --hide-scrollbars --force-device-scale-factor=1 --window-size=2172,724 --screenshot=banner.png docs/assets/source/banner.html
chrome --headless=new --hide-scrollbars --force-device-scale-factor=1 --window-size=1774,887 --screenshot=social-preview.png docs/assets/source/social-preview.html
```

The mark is redrawn as vector shapes from the official avatar in the Relay organization profile. Future artwork should keep depicting the real panes, status bar, and key hints rather than decorative terminal code, agents, robots, or providers.
Binary file added docs/assets/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading