Tabs Adapter
The TabsAdapter interface defines how pok runs multiple commands or tasks inside a tabbed terminal UI.
Contract
The adapter receives a set of tab specs and renders them with buffered output, keyboard navigation, and process lifecycle management:
- Each tab shows buffered output
- Keyboard navigation between tabs
- Scrollable output history
- Process lifecycle management
Interface
interface TabsAdapter {
run(items: TabSpec[], options: TabsOptions): Promise<void>;
}
type TabSpec = {
label: string;
exec: string;
};
type TabsOptions = {
name: string;
cwd: string;
env: Record<string, string | undefined>;
};@pokit/opentui
The default implementation uses OpenTUI:
import { createTabsAdapter } from '@pokit/opentui';
const tabsAdapter = createTabsAdapter();
// Use in router config
await run(args, {
tabs: tabsAdapter,
// ...
});Command usage
run: async (r) => {
await r.tabs([r.exec('npm run dev'), r.exec('npm run watch:css'), r.exec('stripe listen')], {
name: 'Development',
});
};With Tasks
run: async (r) => {
await r.tabs([r.run(devServerTask), r.run(watcherTask), r.exec('stripe listen')], {
name: 'Development',
});
};Tab Labels
Labels are derived from:
- Commands: First word of the command
r.exec('npm run dev'); // Label: "npm" r.exec('stripe listen'); // Label: "stripe" - Tasks with shortLabel: The
shortLabelpropertydefineTask({ label: 'Development Server', shortLabel: 'dev', // Tab shows "dev" exec: 'npm run dev', }); - Tasks without shortLabel: First word of exec or label
Runtime behavior
When r.tabs() is called:
- Reporter suspends: Clack output pauses.
- Full-screen mode: the alternate buffer is activated.
- Processes spawn: all commands start.
- UI renders: the tab bar and output area mount.
- User interacts: tabs switch and output scrolls.
- Processes complete: the adapter exits when the process policy is satisfied.
- Reporter resumes: normal command output returns.
Single item behavior
With a single item, no tabbed UI is shown:
// Just runs directly with inherited stdio
await r.tabs([r.exec('npm run dev')]);Keyboard Controls
The OpenTUI adapter provides:
| Key | Action |
|---|---|
← → or 1-9 | Switch tabs |
↑ ↓ | Scroll output |
q or Ctrl+C | Quit |
Error Handling
If a process fails:
- Tab status shows error indicator
- Other processes continue (configurable)
- Error shown when exiting
No Adapter Provided
If tabs is not in router config:
await r.tabs([...]);
// Error: Tabs adapter not available. Please provide a TabsAdapter...TabSpec Details
type TabSpec = {
label: string; // Display name in tab bar
exec: string; // Shell command to run
};The runner converts r.exec() and r.run() to TabSpecs:
// r.exec('npm run dev') becomes:
{ label: 'npm', exec: 'npm run dev' }
// r.run(task) with env becomes (after resolution):
{ label: 'dev', exec: 'vite --mode dev' }Environment Resolution
Task environments are resolved before tabs render:
await r.tabs([
r.run(taskWithEnv), // Env resolved with loading spinner
r.exec('other'),
]);This shows a "Loading Secrets" group before the tabbed UI.
Custom Adapter
Implement your own adapter:
import type { TabsAdapter, TabSpec, TabsOptions } from '@pokit/core';
const myAdapter: TabsAdapter = {
async run(items: TabSpec[], options: TabsOptions) {
// Spawn processes
// Render UI
// Handle input
// Wait for completion
},
};