@luan.sh/pi-panels

Generic contributed side-panel host for Pi

Packages

Package details

extension

Install @luan.sh/pi-panels from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@luan.sh/pi-panels
Package
@luan.sh/pi-panels
Version
0.3.3
Published
Sep 12, 2026
Downloads
not available
Author
cfcluan
License
MIT
Types
extension
Size
25.6 MB
Dependencies
2 dependencies · 2 peers
Pi manifest JSON
{
  "image": "https://pi.luan.sh/media/previews/pi-panels-e69474de1ee3.png",
  "video": "https://pi.luan.sh/media/previews/pi-panels-ae3c28bfa509.mp4",
  "extensions": [
    "./src/extension.ts",
    "./node_modules/@luan.sh/pi-libtui/src/extension.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

@luan.sh/pi-panels  

@luan.sh/pi-panels is a generic side-panel host for Pi's TUI. It mounts a right-hand split pane and lets other Pi extensions contribute tabs to it. The host owns the split layout, focus, pointer resizing, persisted width and tab order, draggable pill tabs, the empty state, and the top-right show and zoom controls. It contains no feature content of its own: with nothing else installed the panel is empty. Tabs come from other packages, for example @luan.sh/pi-side, @luan.sh/pi-tuicr, @luan.sh/pi-exec-command, @luan.sh/pi-subagents, and @luan.sh/pi-xsettings.

Preview

@luan.sh/pi-panels in Bootty

Watch the demo.

Install

pi install npm:@luan.sh/pi-panels

The package ships its internal dependencies bundled and also loads the shared @luan.sh/pi-libtui extension entry so the registries it relies on exist. It runs only in interactive TUI sessions; it does nothing in print mode, without a UI, or in a child process started with PI_EMBEDDED_SIDE_CHAT=1.

Optional companion: pi install npm:@luan.sh/pi-xsettings provides a shortcut host that binds the keys in your keybindings.json globally; without it, the panel's shortcuts only work while the panel itself has focus, and the panel is opened by contributed tabs or the top-right controls.

Use it

The panel appears when a provider adds a tab, or when you run the toggle action. The first mount takes 50% of the terminal width; dragging the divider persists the new width. Clicking a tab activates it, dragging reorders it, and closing the last tab hides the panel until it is reopened. Two icons in the top-right corner of the screen toggle visibility and expand or restore the panel; the expand icon is shown only while the panel is visible. Resizing the divider while expanded returns the panel to its normal width.

The extension registers these actions through @luan.sh/pi-libactions. It adds no slash commands.

Action Effect
panels.toggle Show or hide the side panel
panels.focus Focus the side panel (showing it first if needed)
panels.main.focus Focus the main session
panels.focus.next Move focus to the other split pane
panels.zoom Expand the panel to the full width, or restore it
panels.tab.previous Select the previous tab
panels.tab.next Select the next tab

Keybindings

None of these actions has a default key. Bindings live in keybindings.json in Pi's agent directory (normally ~/.pi/agent/keybindings.json). The file is a JSON object; each property name is an action ID and each value is a key ID string or an array of key ID strings. The file is read when extensions load, so reload after editing. An example that binds every action group:

{
  "panels.toggle": "ctrl+shift+b",
  "panels.zoom": "alt+shift+z",
  "panels.focus.next": "alt+o",
  "panels.tab.previous": "alt+h",
  "panels.tab.next": "alt+l"
}

While panel content has focus, the host checks the pressed key against the bindings for panels.toggle, panels.zoom, panels.focus.next, panels.tab.previous, and panels.tab.next, then for contributed empty-state actions, then for the active tab's inputActions, before forwarding the key to the tab's component. A matching shortcut runs the action and is not passed on, so an embedded TUI cannot receive it. Header and empty-state buttons show the bound shortcut when one exists. Errors thrown by an action are reported as a notification.

Contribute a tab

Providers register through @luan.sh/pi-libtui, not through this package, so a contributor has no dependency on @luan.sh/pi-panels and must keep working when no host is installed. The contract is exported from @luan.sh/pi-libtui:

import { registerSidePanelProvider, type SidePanelSession } from "@luan.sh/pi-libtui";

const dispose = registerSidePanelProvider(
	{
		id: "my-extension",
		session: context.sessionManager,
		attach(panel: SidePanelSession) {
			panel.addTab({
				id: "my-tab",
				label: "My tab",
				create: (host, theme) => new MyComponent(host, theme),
				onClose: () => cleanUp(),
			});
			return () => panel.removeTab("my-tab");
		},
	},
	globalThis,
);

registerSidePanelProvider stores the provider in a process-wide registry keyed by Symbol.for("pi-panels/registry/v1"). When this host is present it calls attach with a SidePanelSession, but only for providers whose session matches the current Pi session; the returned function runs when the provider is replaced, unregistered, or the session detaches. Registering a provider with an ID that is already registered replaces it. When no host is installed, attach is never called, so a provider that needs a fallback should check ensureSidePanelRegistry(globalThis).hasHost() and, for example, open a fullscreen overlay instead. Errors thrown by attach are reported through context.ui.notify and do not break the host.

A SidePanelTab has id, label, an optional icon, an optional headerAction ({ label, actionId }, shown as a dropdown button at the right of the tab bar), optional inputActions (action IDs whose shortcuts the host intercepts while this tab is focused), create(host, theme), and an optional onClose called when the user closes the tab. addTab accepts { activate, focus } options; by default it activates the new tab, reveals the panel, and focuses it. The SidePanelSession also offers restoreTab (re-adds a tab without revealing the panel or persisting state), updateTab, removeTab, activate, activeTabId, registerEmptyAction (a labelled button shown when no tabs exist, returning a disposer), show, toggle, toggleZoom, focus, focusMain, focusNext, activatePrevious, activateNext, isVisible, isZoomed, and requestRender.

Settings

The package has no settings and does not use @luan.sh/pi-xsettings. Layout is persisted per session as a custom session entry of type side-panel:layout-v1 holding visible, width, order, and activeTabId; the most recent valid entry on the current branch is restored on session start. Persisted state does not include tab contents, so providers must re-add their tabs (usually with restoreTab).

Layout

Responsibility Owner
Contribution protocol, registry, content types @luan.sh/pi-libtui (panels.ts)
Pi registration, host install, top-right controls src/extension.ts
Panel lifecycle, tab bookkeeping, split-pane mount, zoom src/controller.ts
Persisted layout state and its parser src/state.ts
Tab bar, header action, empty state, input routing src/view.ts
Action registration src/actions.ts

Develop

Source: https://github.com/luan/agents, directory harnesses/pi/agent/packages/pi-panels. Run bun run typecheck and bun test test in that directory.