An open-source macOS terminal workspace with real shells, tabs, groups, and resizable split panes.
An open-source macOS terminal workspace with real shells, tabs, groups, and resizable split panes.
curl -fsSL https://qentrah.com/qh/install.sh -o /tmp/qh-install.sh
sh /tmp/qh-install.sh
~/.local/bin/qhRead the original README for the complete repository reference.
QH by Qentrah. A clean, open-source macOS terminal workspace with real shells, tabs, groups, and resizable split panes. Use it as a desktop app or run the OpenTUI interface inside your existing terminal.
Built by Qentrah with OpenTUI, React, TypeScript, and adapted TermCN components. MIT licensed.
Website & screenshots · Download the macOS app · Report an issue · Contribute
Drag a split line to resize that branch. Drag the center junction to resize all connected splits together, or use the arrow keys while the junction is focused.
QH includes the Node.js qh command. The managed macOS installer bundles both Node and Bun:
curl -fsSL https://qentrah.com/qh/install.sh -o /tmp/qh-install.sh
sh /tmp/qh-install.sh
~/.local/bin/qh help
~/.local/bin/qhThe installer downloads the native Apple Silicon or Intel release from GitHub, verifies SHA-256 and the app signature, and installs into a versioned local folder. It creates a qh launcher in ~/.local/bin; add that folder to your shell PATH if necessary. It refuses to replace unrelated commands. QH_BIN_DIR and QH_INSTALL_DIR choose other destinations. The app is locally signed and not notarized.
qh help # List commands
qh search update # Search built-in and custom commands
qh config # Open the global configuration
qh config path # Print its path
qh config validate # Validate JSON and settings
qh update --check # Check QH releases
qh update # Update a managed install if a newer version exists
qh update-all --dry-run
qh update-all # Run only the updates you configured
qh workspace list # List saved workspace records
qh workspace Research # Open a named CLI workspacegh remains GitHub CLI. QH uses qh. For a source checkout, run ./bin/qh; update the checkout with Git rather than the managed installer. Node.js 22.13+ and Bun 1.3.5+ are required for source development. Packaged downloads supply their runtimes.
In the desktop app, open Projects from the bottom of the sidebar. Search for a project, choose npm or Bun for a published CLI, select a folder to clone source, fork on GitHub, or fill out a bug report. Actions show progress and errors beside the terminal while your shells keep running.
The bundled qh can discover every public Qentrah repository, install published CLIs, clone source, fork projects, and report bugs:
qh projects
qh project Kyna
qh install Kyna --manager bun --dry-run
qh install DUI --manager npm
qh install kyna-music --source --dir ./music
qh clone qh ./qh-source
qh fork qh
qh bugs qh
qh bug qh --title "Resize issue" --body-file ./report.mdRemove --dry-run to execute an inspected action. Source checkouts require Git; forks and issues require GitHub CLI and gh auth login. Bug reports contain only text you provide. Repositories without published CLIs are checked out from source; their build instructions are printed. qh projects --live fetches the current public catalog.
For a lightweight CLI without the desktop app, install the standalone package with npm or Bun:
npm install --global https://github.com/qentrah/qh/releases/latest/download/qentrah-qh.tgz
# Or:
bun add --global https://github.com/qentrah/qh/releases/latest/download/qentrah-qh.tgzAn existing QH installation already supplies qh; use that command rather than overwriting its launcher. See standalone CLI documentation.
Add command objects to your global configuration. No CLI updates are enabled by default. Inside a QH shell, typing qh shows help rather than opening a nested workspace. Zsh Tab completion lists built-in and configured command names.
{
"commands": {
"dev": {
"command": "npm run dev",
"cwd": "~/Documents/my-project",
"description": "Start the development server"
}
},
"updates": {
"codex": { "command": "npm install -g @openai/codex" },
"claude": { "command": "npm install -g @anthropic-ai/claude-code" },
"opencode": { "command": "opencode upgrade" }
}
}Run qh dev to execute a configured command with your shell and permissions. Desktop Commands lists these shortcuts and starts the selected command in a new tab. Command names use lowercase letters, digits, and hyphens; built-in names are reserved. Custom command arguments belong in the JSON command string. qh update-all runs the configured list in order and stops on the first failure. It does not discover or change other installed CLIs. updateChannel accepts preview (default) or stable.
Workspace layouts are stored in ~/Library/Application Support/Qentrah/QH/workspaces.sqlite. There is no server database or synchronization. The store records workspaces, tabs, groups, split ratios, focus, colors, session labels, and working directories. Directory detection follows the shell's current directory through macOS process information. Missing folders fall back to the home folder.
On first launch after the QH rename, existing settings and saved workspaces are imported from the previous application data folder. Installed versions stay in place. QH_DATA_DIR selects an isolated data folder and bypasses this import.
On reopen, QH creates fresh shell processes in those folders. It does not revive exited programs, reuse their PIDs, replay commands, or restore terminal output. Set restore to false for a fresh layout. Independent windows have separate saved records. Closing a workspace removes it from that record.
The sidebar workspace and tab three-dot menus expose rename, color, new tab, split, and close actions. Double-click a workspace, tab, or shell label to rename it directly in the sidebar; Enter saves and Escape cancels. The bottom toolbar provides New, Commands, Settings, and the global menu. Workspace colors provide a default accent for their tabs; individual tab colors override it. Live-session closure asks for confirmation.
Foreground process names and terminal titles identify OpenCode, Claude, and Codex where available. Detection is best effort; wrappers can hide the program name. Rename a session label manually when needed. Configure apps entries with name, literal match, and an icon of opencode, claude, codex, or shell. The Codex entry uses the OpenAI mark. See NOTICE.md for sources and trademark attribution.
Layouts restore from the local SQLite store unless restore is disabled. Reopened shells have new PIDs.
Get QH-macOS-arm64.zip from Releases, extract it, and open QH.app. You can move the app to Applications or add it to the Dock. Settings are stored in your local Application Support folder; no sidecar file is needed.
The supplied build targets Apple Silicon macOS and includes Electron, Bun, Node.js, and native terminal dependencies. No separate runtime install is needed for the downloadable app. These early builds are locally signed and not notarized. The repository contains the source and reproducible build instructions.
Requires macOS, Bun 1.3.5+, and Node.js 22.13+ on PATH. Development is verified with Bun 1.3.5 and Node.js 24.12.0.
git clone https://github.com/qentrah/qh.git
cd qh
bun install
# Run inside your existing terminal
bun run start
# Or open the desktop development window
bun run desktopThe desktop command builds its renderer and copies the required xterm stylesheet before starting Electron. The CLI starts shells in the launch directory; the desktop uses defaultPath from the configuration JSON.
To use the short CLI command anywhere:
bun link
cd /path/to/your/project
qhqh --help prints usage. The older q-terminal and qentrah-terminal command aliases remain available. The legacy Bun aliases use $SHELL, falling back to /bin/zsh; global configuration selects the desktop and qh shell.
bun install runs the setup script to fix permissions on node-pty's macOS spawn helper. If lifecycle scripts were disabled, run bun run setup. Electron is a trusted dependency so its official binary installer can run.
If a native prebuilt dependency is unavailable:
xcode-select --install
npm rebuild node-pty --build-from-source
bun run setupSet QENTRAH_NODE to an absolute Node executable if it is not available as node on PATH. Desktop development finds Bun at ~/.bun/bin/bun by default; set QENTRAH_BUN if yours is installed elsewhere. Packaged apps use their bundled runtimes.
The three-dot menu exposes New Tab, New Window, Configure…, Split Right, Split Down, New Group…, Open / Create Folder…, Commands…, Keyboard Shortcuts, and About Qentrah.
| Desktop shortcut | Action |
|---|---|
| Cmd+T | New tab |
| Cmd+N | New independent window |
| Cmd+, | Settings |
| Cmd+Shift+P | Search commands |
| Cmd+Q | Quit all windows, with confirmation |
Click a sidebar tab to switch. Double-click its name to rename it. The close button appears on hover or keyboard focus. Drag tabs to reorder them or drop them on a named group. Drag a tab or shell row to an edge of the terminal area to dock it. The session menu’s Move to new tab action detaches a split shell into its own tab. These actions preserve shell processes.
Drag the gap between split panes to resize them, or use the prefix shortcuts below. The sidebar edge is also draggable; focus it and press Left/Right for keyboard resizing. Groups organize tabs and are restored with the saved layout. Open / Create Folder uses the native macOS folder picker and starts a shell in the chosen filesystem folder.
Open Commands and type /help, /config, /new-tab, /new-window, /split-right, /split-down, /group, /folder, or /about. These are application commands entered in the Commands panel; ordinary shell input stays with the shell.
Press Ctrl+B, release it, then press a command. They work in the CLI and desktop app.
| Command | Action |
|---|---|
c | New tab |
n / p | Next / previous tab |
% / " | Side-by-side / stacked split |
| Arrows | Focus a neighboring pane |
| Shift+arrows | Resize the nearest matching divider |
r | Rename active tab |
x / w | Close active pane / tab |
| Page Up / Page Down | Scroll history |
y | Copy pane selection using macOS pbcopy |
| Enter | Restart an exited or failed shell |
? | Keyboard help |
q | Quit workspace |
| Ctrl+B again | Forward a literal Ctrl+B to the shell |
| Escape | Cancel pending prefix command |
Ordinary keys, including Ctrl+C, go to the focused shell. Click a pane to focus it; drag over text to select it. The wheel scrolls the pane under the pointer. Live-session dialogs default to Cancel. Tab changes the selected action and Enter accepts; Escape cancels.
Pane minimums are 20 columns and 5 rows. When a window is too small for the split tree, only the focused pane appears. Prefix+arrows cycles through hidden panes. Enlarging the window restores the layout and saved split ratios.
Settings… opens a separate, resizable window with Appearance, Terminal, Storage, Keyboard, Commands, Updates, and About pages. Changes save automatically and show Saved after validation and an atomic write. Open Configuration JSON opens ~/Library/Application Support/Qentrah/QH/config.jsonc in VS Code, Cursor, Windsurf, or the associated editor. JSONC accepts comments and trailing commas; documentation links are included at the top. Editor changes are reloaded automatically in open desktop windows. Invalid edits leave the active settings unchanged. The previous sidecar qh.config.json is imported on first use if no global configuration exists. Use qh config path to find the active file.
{
"defaultPath": "~",
"shell": "/bin/zsh",
"fontFamily": "Menlo, Monaco, monospace",
"fontSize": 14,
"tabsPosition": "left",
"theme": {
"background": "#0e0d11",
"surface": "#141318",
"foreground": "#d4d4d4",
"muted": "#a09ca8",
"border": "#45414e",
"primary": "#9cdcfe",
"error": "#f48771",
"selection": "#264f78"
}
}defaultPath accepts an absolute folder path or ~/~/…. shell must be an absolute executable path. Font size is 10–28; tabs can be left or top; theme colors use #RRGGBB. Invalid edits leave the previous configuration intact. A missing starting folder falls back to the home folder.
Saving updates font and tab placement in existing windows. Theme changes apply immediately. Open a new window to apply the starting folder or shell. Editing settings does not restart existing shells.
Zsh command names use yellow, strings orange, keywords purple, variables/options blue, comments green, and unknown commands red. Unknown-command highlighting is feedback from zsh; it does not mean typing failed. Programs control their own true-color output; plain file output is not automatically syntax highlighted.
A scoped ZDOTDIR wrapper loads the user's .zshenv, .zprofile, .zshrc, and .zlogin in order. It does not rewrite dotfiles. History stays in the user's normal location rather than the app bundle. To retain your original prompt and highlighting:
QENTRAH_SHELL_STYLE=original qhbun install
bun run build:appThis builds ../QH.app and places a default qh.config.json beside it if one does not exist. To choose another path:
node scripts/build-macos-app.mjs /path/to/QH.appThe build refuses to overwrite an existing app. It bundles the local Bun and Node binaries, dependencies, source, licenses, and Qentrah icon, then applies and verifies a local signature. Build on the Mac architecture you intend to distribute; the current release includes native arm64 and x64 builds. Signing with a Developer ID and notarization are not part of this build script.
To regenerate the icon, install Pillow and run python3 scripts/generate-app-icon.py. To build only desktop renderer assets, run bun run build:desktop.
Current limits: macOS first; restoration starts fresh shells and does not resume previous programs or terminal output; no AI features or website integration. Workspace mouse events support focus, selection, and resizing; mouse input is not forwarded to child terminal programs. Fonts and glyph coverage depend on the selected terminal font.
bun run check
bun test
node scripts/smoke-cli.mjs
node scripts/smoke-desktop.mjs
node scripts/smoke-restore.mjs
bun run build:desktopThe macOS checks and builds workflow runs these checks on Apple Silicon (arm64) and Intel (x64), builds a standalone app on each native runner, smoke-tests its bundled Node/Bun runtimes and shell sessions, and verifies the app signature afterward. Pull requests run the checks without publishing. Every push to main, including a merged pull request, builds both native Mac architectures and automatically publishes the next stable version after both jobs succeed. The workflow embeds that version in the app, verifies the ZIP checksums, uploads both ZIPs, checksums, and install.sh to a draft, then publishes it. No manual tag or website version update is needed. Explicit v* tags also publish; tags with a prerelease suffix remain previews. Reruns preserve assets of an already published release. Build artifacts are retained for 30 days. Builds use a local signature and are not notarized. Tests cover shell history/completion, Unicode, colors, multiline paste, Ctrl+C, vim/top, tab isolation, PID preservation, docking/detaching, config validation, geometry, narrow-window restoration, confirmation, copying, startup, and cleanup. Clipboard tests inject an adapter without changing the system clipboard. Smoke checks run real PTYs and verify cursor placement and process cleanup.
See CONTRIBUTING.md for contributions and AGENTS.md for agent instructions. Bugs and feature requests belong in Issues.
QH's original source is MIT, copyright Qentrah. Third-party code retains its own licenses; see NOTICE.md, the vendored license files, and notices bundled with the desktop app. The QH logo remains a brand mark; the software license does not grant trademark rights.
Use the gear in the sidebar or Cmd+, to open Settings. Settings opens in a separate native window; closing it preserves the terminal sessions. Rename workspaces, tabs, pane labels, and groups inline with a double-click. Tab and workspace Color menus offer Cyan, Blue, Green, Amber, Violet, and Rose, Default, plus the macOS color chooser.
GUI shortcuts map action names to Electron accelerators, such as "new-tab": "Cmd+T". Record them on the Keyboard page; Backspace disables a binding, Escape cancels, and duplicate or reserved editing/shell keys are rejected. This does not change the terminal Ctrl+B prefix. The config file links to configuration and keyboard customization.
storage.enabled enables local SQLite layout saves; restore enables restore on launch. storage.saveDelayMs batches saves (100–5000 ms), storage.maxWorkspaces limits new workspaces (1–100), and scrollback bounds per-shell emulator history (100–50000 lines). Existing workspaces survive lowering the creation limit. SQLite stores layout metadata and folders; terminal output, running processes, secrets, and environment variables are not saved. Storage shows the database path and size. Font, colors, tab position, scrollback, and creation limits apply immediately; shell and starting folder apply to new windows.
Appearance includes Charcoal, Graphite, and Midnight themes. Save the current palette with a name to add a custom theme. Close Settings with the native window close button. Commands closes with its × button or Escape. Context menus and theme selectors close when you click outside. Each workspace menu has New tab for that workspace. Recent app errors are bounded to 30 entries in memory, visible in Storage, and never uploaded.
The sidebar displays every workspace, its tabs, and connected session rows. Select a session to focus its workspace, tab, and pane. Drag tabs and session rows, or a split session’s header, to the Left, Right, Above, or Below target on a specific pane in the active workspace. This supports stacked sessions beside a full-height pane. Select another workspace before docking its items. The lower toolbar groups Terminal, New, Commands, Settings, and the global menu. Workspace and global menus include New tab and ready-to-use themes. Window size, sidebar width, last Settings page, and last app view are stored in local SQLite when saving is enabled. qh diagnostics validates configuration and prints storage location plus the last saved app startup, main-process memory, and session count. Performance samples stay local. Bundled qh is on the PATH of all app shells, including restored shells.
Theme, tab position, and release channel use custom dropdowns with arrow/Home/End navigation, typeahead, Escape, and outside-click dismissal. Settings dimensions and the last page are saved separately in SQLite.
Workspace names toggle the tab tree on click and rename on double-click. Item menus appear on hover or keyboard focus. Session menus offer new split sessions, a new tab in that folder, rename, detach, restart after exit, and close. Deleting a workspace removes its saved layout and closes its sessions after native confirmation. The global menu sits at the bottom of the sidebar and includes themes, update checks, shortcuts, and About.
New tabs inherit the focused shell's working directory. Bundled qh and Node are restored to the zsh PATH after user login files, including restored sessions. Equalize Splits resets split ratios without restarting shells. See terminal design notes for Ghostty references and the tested limits.
Mouse wheel input now follows the inner terminal mode: normal shell output scrolls history, alternate-screen pagers receive arrow keys, and mouse-enabled CLIs receive SGR, UTF-8, or legacy mouse reports. Shift overrides mouse reporting for text selection/history. Desktop split dividers have a 10-pixel grab area, a resize cursor, and a grip on hover or keyboard focus. Drag either divider in a nested split or focus it and use its arrow keys. The terminal CLI retains its cell-based dividers. Session/tab actions resolve IDs across workspaces, reject stale targets, and preserve other shell processes. Double-click rename waits for the click sequence instead of losing its target during a sidebar rebuild. Default workspace/tab names are unique; split shell labels are numbered unless renamed.
Click the active Terminal button to collapse the sidebar into a narrow action rail; click it again to reopen the tree. This preference is saved locally. A split junction uses a diagonal resize cursor and adjusts both split directions. Foreground identities include Pi, Codex, Claude, OpenCode, Gemini, Aider, GitHub CLI, Node.js, and Python. Node wrappers are inspected locally; raw command arguments are not stored or sent to the GUI.
qh install latest and qh install stable install the latest complete stable release. qh install latest version is an alias. Use qh install v0.6.10 for a specific release or qh install preview to include previews. Add --check to resolve the version without installing. qh update follows the configured channel; qh update stable or qh update latest selects stable releases. Updates never downgrade a newer install. Explicit installs may select an older version.
The bootstrap script accepts the same latest, stable, preview, or version selector, resolves it from GitHub, and verifies the downloaded app checksum and signature. The public installer URL redirects to the installer shipped in the latest stable GitHub Actions release. The app’s update check uses the same complete-release selection rules as the CLI.