Menu Bar App
Command palette, window tiling, and session management
The lattices menu bar app sits in your menu bar and controls your workspace from there.
Installation
lats app # Build (or download) and launch
lats app install # Register launch-at-login and launch now
lats app update # Swap in the latest app release only
lats app build # Rebuild from source
lats app restart # Quit, rebuild, relaunch
lats app quit # Stop the appThe first run builds from source if Swift is available, otherwise downloads a pre-built binary from GitHub releases.
Use lats app install when you want the companion to start now and
open automatically at login. It installs a user LaunchAgent in
~/Library/LaunchAgents; inspect it with lats app login status
and remove it with lats app login disable.
Updating
lats update # Update the CLI and app togetherlats update reinstalls @lattices/lattices with the package manager
that owns your global install (npm, bun, or pnpm). It re-registers
launch-at-login so the LaunchAgent points at the new install, and it
relaunches the app if it was running. Pass --force to reinstall when
you already have the latest version.
Command palette
Press Control+Option+Space from anywhere to open the command palette. It's a searchable list of every action the app can perform, with fuzzy matching on titles and subtitles.
Project commands
| Command | Description |
|---|---|
| Launch project | Create a new session and open terminal |
| Attach project | Focus or open the running session |
| Sync project | Reconcile session to declared config |
| Restart pane in project | Kill and re-run a specific pane's command |
Window commands
Available for running sessions:
| Command | Description |
|---|---|
| Go to project | Focus the terminal window (switches Spaces if needed) |
| Tile project Left | Snap window to left half |
| Tile project Right | Snap window to right half |
| Maximize project | Expand window to fill screen |
| Detach project | Disconnect clients, keep session alive |
| Kill project | Terminate the tmux session |
Tab group commands
Available when groups are configured in ~/.lattices/workspace.json
(see Tab Groups):
| Command | Description |
|---|---|
| Launch group | Start the group session (all tabs) |
| Attach group | Focus the running group session |
| Group: Tab | Switch to a specific tab within a group |
| Kill group Group | Terminate the group session |
Layer commands
Available when layers are configured in ~/.lattices/workspace.json
(see Layers):
| Command | Description |
|---|---|
| Switch to Layer: label | Focus and tile the layer's project windows |
| Show All Windows | Put back what layer switches parked or hid |
App commands
| Command | Description |
|---|---|
| Settings | Open preferences (terminal, scan root) |
| Update Lattices | Download the latest release and relaunch |
| Diagnostics | View logs and debug info |
| Refresh Projects | Re-scan .lattices.json configs, reload workspace.json |
| Quit Lattices | Exit the menu bar app |
Overlay actors and HUDs
Persistent overlay actors can be hidden or restored with Hyper+B. Apps can publish a static hover dashboard by exposing:
.lattices/hud/manifest.jsonRegister and publish one:
lats hud register .lattices/hud/manifest.json --publishThe manifest points to a local index.html, optional icon, app activation
target, HUD dimensions, and optional app-owned sources metadata for logs or
state files. Lattices hosts the actor and loads the dashboard through a
transparent WKWebView; the app owns the renderer and writes its own logs in
the places it already uses.
Project discovery
The app scans a configurable root directory (up to 3 levels deep)
for .lattices.json files. It skips .git/ and node_modules/.
Auto-detection for the scan root checks these paths in order:
~/dev, ~/Developer, ~/projects, ~/src.
For each project found, the app reads:
- Pane names and commands from
.lattices.json - Dev command and package manager from
package.json - Running status by checking
tmux has-session
Session management
The app calls the lattices CLI for session operations. Launch runs
lats start in the project directory, Sync runs lats sync to
reconcile panes, and Restart runs lats restart <pane> to kill
and re-run a pane's process. Detach and Kill call tmux detach-client
and tmux kill-session directly.
Window tiling
The app can tile terminal windows to preset screen positions via
the command palette. It finds windows by their [lattices:session-name]
title tag.
For Terminal.app and iTerm2, tiling uses AppleScript to set window bounds by matching the title tag. For other terminals, it tiles the frontmost window.
Tile positions (app)
| Position | Area |
|---|---|
| Left | Left half |
| Right | Right half |
| Top Left | Top-left quarter |
| Top Right | Top-right quarter |
| Bottom Left | Bottom-left quarter |
| Bottom Right | Bottom-right quarter |
| Maximize | Full visible screen |
| Left Third | Left third |
| Center Third | Center third |
| Right Third | Right third |
| Center | 80% width, 80% height, centered (20% margin all sides) |
Moving windows between displays and desktops
In Overview, select a window and open Move in the floating controls. Each destination includes the display name and desktop number, such as DELL S3422DWG · Desktop 2 or Action Agent Layer · Desktop 2. Bring Here selects the current desktop on the display that contains the Overview window.
Drag a window shape onto another display map to move it to that display's current desktop. Drop it onto a desktop thumbnail to choose that exact desktop. The destination highlight identifies the drop target. Selecting a window shows its controls over the canvas; the display maps and arrangement tray keep their positions.
Desktop Inventory and Studio also offer window movement in their context menus:
- Bring Here moves the selection to the UI's display and current desktop.
- Move to Desktop lists each display and its desktops.
- Move to Next Monitor cycles through displays and preserves normalized window position and size.
- Move to Monitor selects a display's current desktop.
- Move & Place selects a display and a placement slot for one window.
A right-click on a member of a multi-selection targets the whole selection. Each window keeps its own normalized frame. Move & Place is available for a single window. Desktop destinations remain available with one display.
The UI, CLI, and daemon use the same verified movement operation. If a window is on an inactive desktop, Lattices first carries it onto its source display's current desktop. It then transfers the window to the chosen display and desktop. Mission Control can appear during the move. Keep the pointer free until the operation finishes.
A move succeeds only after Lattices verifies the final display, desktop, and frame. If a step fails, Lattices attempts to restore the original location and reports the failure. The interface keeps the selection and refreshes the observed window state.
Space navigation
"Go to" commands can switch macOS Spaces to reach a window on a different desktop. The app uses a three-path fallback:
- CGWindowList (needs Screen Recording) — looks up the window by title tag, finds its Space via SkyLight, switches to it, then raises the window
- Accessibility API (needs Accessibility) — finds the window via AXUIElement, raises it, and activates the app
- AppleScript — iterates windows by name for Terminal/iTerm2, or bare-activates for other terminals
When a window is found and focused, the app flashes a green border highlight around it for ~1 second so you can spot it immediately.
Grant Screen Recording and Accessibility permissions in System Settings > Privacy & Security for all three paths to work.
Instant space switching
With Accessibility permission, the app intercepts Ctrl+← / Ctrl+→ before Mission Control and switches Spaces through a synthetic Dock swipe under a screen-update freeze — the real switch path, so Dock's space model, Mission Control, and space-change observers all stay consistent, while the freeze makes it render as an instant cut. A desktop filmstrip appears mid-screen over a subtle neutral dimmed backdrop. The focus frame stays centered while window-layout previews slide through it; the small rail below represents every desktop. The preview reads the existing desktop inventory without taking screenshots or scanning windows on each keypress. Pressing past the first or last desktop is silent. Reduce Motion updates the selection without sliding; Reduce Transparency makes the tray and canvas backdrop opaque.
While the filmstrip is visible, Control+Up, Control+Down, or Control+minus opens the full connected desktop canvas in one motion. You can also open it directly under the pointer: hold the right mouse button and pull upward, or hold Control+Option and scroll down. An ordinary right-click still opens its context menu. The opening scroll's remaining movement and momentum are consumed so they cannot immediately change the new canvas camera.
The canvas stays open when you release the button or modifiers. Moving the pointer highlights a desktop without moving the canvas; Left/Right or a single click also chooses it. Up (with or without Control) and plus zoom toward the highlighted desktop. Down and minus zoom back out. Enter or a double-click lands there; Escape cancels without switching. Zooming never lands. Control+Option+scroll zooms around the pointer, ordinary scrolling pans, and the bottom-left minimap repositions the view. Pinch, Command+scroll, and the zoom buttons also work. Fit shows all desktops. Text fields keep their arrow keys. The Control+Option tiling HUD and Spatial Lens stay inactive while this navigator owns the gesture.
Every page in the main window has a Zoom out control in its header. It opens the same connected desktop canvas over the current page. Back to… returns to that page with its draft, filters, and local view state intact. Choosing a sidebar page leaves the canvas and navigates normally.
Agents can move the tray via
settings.spaceBezel.set (top, bottom, center, or travelEdge
to dock at the edge of travel). Desktop shimmer in Settings > Keyboard
adjusts the passing light from Off to its original strength, with a subtle
15% default. Rapid switches share one pass instead of stacking light. Changes
apply on the next switch without restarting; Reduce Motion and Reduce
Transparency suppress the effect. Agents can also set it with
settings.spaceBezel.set {"shimmerIntensity":0.15} (range 0–1; zero is off).
The filmstrip stays visible while the switch is in progress, then waits 0.85
seconds after landing before its 0.12-second fade.
Only bare Ctrl+arrows are claimed by Instant Space Switching;
Ctrl+Option+arrows tile, and Ctrl+Option+Shift+arrows browse layers. While the feature is on, the app
disables Mission Control's "Move left/right a space" symbolic hotkeys
(they would replay the slide animation); the original setting is
restored when the feature is toggled off or the app quits. Toggle it in
Settings > Keyboard > "Instant Space Switching".
Voice commands
See Voice Commands for the full guide.
Press Hyper+D to open the voice command window. Hold Option to speak, release to stop. Lattices transcribes via Vox, matches to an intent, and executes. Built-in commands: find, show, open, tile, kill, scan.
The provider-backed assistant can run in parallel, offering follow-up suggestions in the AI corner. Configure the provider and credentials in Settings > Voice. Workspace chat itself uses Scout and needs no separate key.
Settings
Open via the command palette or the gear icon in the main view. The settings window has five tabs:
General
| Setting | Description |
|---|---|
| Terminal | Which terminal to use (auto-detected from installed) |
| Mode | learning or auto (see below) |
| Scan Root | Directory to scan for .lattices.json configs (type a path or click Browse) |
| Updates | Download the latest release and relaunch the app |
| Keyboard Remaps | Optional Caps Lock layer that maps hold to Hyper and tap to Escape |
Mode controls how the app handles session interaction:
- Learning — shows tmux keybinding hints when you detach (helpful while getting used to tmux)
- Auto — detaches sessions automatically (fewer prompts)
Keyboard Remaps
Keyboard remaps are enabled by default for the laptop-friendly rule:
- hold Caps Lock -> Hyper (
Control+Option+Shift+Command) - tap Caps Lock -> Escape
Holding Control + Option is quiet by default. Control+Option+arrows tile the current window; add Shift to browse layers. Existing custom bindings remain available in Settings > Shortcuts.
The pointer picker is optional under Settings > Shortcuts > Ctrl+Option hold. With Spatial Lens enabled, holding physical Control + Option reveals eight contextual placement targets around the pointer. Choose a side to preview left, right, top, or bottom placement, or choose a corner for quarter placement. A deliberate outer flick well beyond a side target previews the physically adjacent display in that direction; release either modifier to commit the one visible proposal. Pull back before release to return to the original display; release in the neutral center or press another key to cancel.
Repeated placement follows the window's live geometry: half, third, two-thirds, then half again. The exact Control + Option hold deliberately excludes Command and Shift, so Caps Lock continues to own the Hyper layer.
Rules live in ~/.lattices/keyboard-remaps.json, and the Settings toggle
can turn the layer off. Keyboard remaps require Accessibility permission
because they use a local event tap.
Companion
Shows the secure local bridge status, Mac bridge fingerprint, supported capability grants, and paired iPad or iPhone devices. The paired-device list shows each device fingerprint, last-seen time, and granted capabilities. You can refresh the list, revoke an individual device, or forget all trusted companions.
The local companion bridge and trackpad proxy are off by default for
privacy. Turn the bridge on in Settings > Companion, or open
lattices://companion/enable to enable the bridge and jump straight to
Companion settings. lattices://companion/disable turns it off again.
The trackpad proxy toggle lives here. Paired devices still need the
input.trackpad grant before they can send pointer events.
AI
| Setting | Default | Description |
|---|---|---|
| Workspace chat | Scout | Uses the existing local Scout broker and keeps a persistent Scout ref; no separate Lattices chat credential |
| Voice provider | OpenAI | Optional voice-only HudsonAI provider for interpretation and speech services |
| Provider credentials | Not set | API-key storage in the macOS Keychain for the selected provider |
Shows assistant readiness and the selected provider's authentication state.
Shortcuts
Shows keyboard shortcut reference. These are the defaults; change them from Settings > Shortcuts. Hyper means Control+Option+Shift+Command. With keyboard remaps on, holding Caps Lock is Hyper.
Tiling and placement:
| Shortcut | Action |
|---|---|
| Control+Option+arrows | Tile the front window to a half |
| Control+Option+Return | Maximize |
| Control+Option+C | Center |
| Control+Option+U / I / J / K | Top-left, top-right, bottom-left, bottom-right quarter |
| Control+Option+Shift+1 / 2 / 3 | Left, center, right third |
| Hold Control+Option, press 1–9 | With Tile HUD enabled, tile to that cell of the 3×3 matrix; 5 maximizes. Number row and numpad both work |
| Hold Control+Option, move the pointer | With Tile HUD enabled, aim a placement; release to tile, stay centered to cancel |
| Control+Option+D | Distribute visible windows |
| Control+Option+O | Organize the front app's windows |
| Control+Option+V | Fill an open 3x2 cell |
| Control+Option+G | 4x4 grid placement |
| Hyper+Y | Grid the visible windows by the front window's type |
| Hyper+Space | Hyperspace window survey |
| Hyper+G | In-place window tools |
| Hyper+H | Window navigation hints |
| Hyper+Z | Focus mode |
| Hyper+X | Stage |
Control+Option pointer controls are off by default. Choose Tile HUD or Spatial Lens in Settings to enable one of those placement modes.
Spaces and layers:
| Shortcut | Action |
|---|---|
| Control+Left / Right | Instant Space switch |
| Command+Option+1–9 | Aim the layer in that pad slot; release to switch. 5 shows the current layer |
| Control+Option+Shift+arrows | Move across the layer pad |
| Command+Option+T | Add the front window to the active layer |
| Command+Option+0 | Classic: every window back, as if Lattices were not running |
| Command+Option+Z | Undo the last layer edit |
Windows and panels:
| Shortcut | Action |
|---|---|
| Control+Option+Space | Command bar: browse, search, and slash commands |
| Hyper+1 | Workspace home |
| Hyper+2 | Window bezel |
| Hyper+3 | HUD |
| Hyper+6 | Shortcuts settings |
| Hyper+7 | Find the mouse |
| Hyper+L | Overview |
| Control+Command+M | Hands-off mode |
| Command+Shift+A | Workspace assistant |
| Command+Shift+L | Activity log |
Voice Command and Search have no default binding. Assign one in Settings > Shortcuts.
Inside tmux:
| Shortcut | Action |
|---|---|
| Ctrl+B D | Detach from session |
| Ctrl+B X | Kill current pane |
| Ctrl+B Left/Right | Move between panes |
| Ctrl+B Z | Zoom pane (toggle) |
| Ctrl+B [ | Scroll mode |
Hyper+L opens Studio on its Overview page; close it with Escape or q when the map owns keyboard
focus, or use the window close button. Hyper+3 opens or dismisses the HUD and
its miniature workspace map. The Scattered and Full HUD presets intentionally
retain a faint, non-interactive ambient surface after dismissal. Neither UI is
the read-only terminal lats map command; see that
page for exact side effects, full-teardown steps, and tab-stack cleanup.
Studio's Layers panel lists the Cmd+Option layers in pad order. Picking one scopes the canvas to the layer's windows that are showing, and the inspector lists each of its entries with the windows it matched and where each one is: on another desktop or display, parked, hidden, or nothing, when no window matches. Picking a layer only looks; the inspector's Cmd+Option key switches to it.
In-place window tools
Press Hyper+G to arrange the real windows on the current desktop without switching to a thumbnail survey. Hover a real app window to reveal its dashed click to select outline, then click it to add or remove it from the selection. Selected windows have solid green numbered outlines; the last one selected is the active target for single-window actions. You can also select from the Windows drawer in the header or use letter hints. The bottom shelf shows the three-step flow—Select, Arrange, Finish—and groups actions by timing:
- Fill expands the active selected window into open space immediately.
- Grid moves two or more selected windows into a balanced grid immediately.
- Swap exchanges the first two selected windows.
- Place stages an exact grid position for the active selected window; it applies only when you Keep changes.
- More exposes tab groups, layers, quick placements, and Hyperspace.
Grid, Fill, and Swap update the live desktop while Hyper+G remains open. Press Keep changes or Return to accept those live moves and apply staged changes. Press Restore or Escape to discard staged changes and restore the window frames and stacking order from when Hyper+G opened. Inline Now and Keep labels distinguish immediate actions from staged actions before anything moves.
Snapshot starts a three-second countdown, captures the complete display
without closing or changing Hyper+G, copies the PNG to the clipboard, and saves
the same image as a Runs artifact. The command-line equivalent is
lats capture display [index] --clipboard --delay 3.
Agent screenshots
When a screenshot is requested through the daemon socket (lattices capture window|display, or any capture.screenshot* call), Lattices draws a coral
hairline around what was captured for about a second. A tag names the agent,
where it called from (this Mac, or ssh <ip>) and its project. The cue appears
after the shot and is excluded from screen capture, so it never shows up in an
image. Screenshots Lattices takes for itself don't trigger it.
Each one is also appended to ~/.lattices/audit/captures.jsonl with the caller,
target and artifact path. Read it with lattices capture log [--limit N] [--json].
The CLI detects Claude Code, Codex, opencode and Cursor from their environment.
Set LATTICES_AGENT to name any other agent.
Docs
Embedded quick reference with glossary, "how it works" steps, and
links to open the full config.md and concepts.md docs.
Supported terminals
| Terminal | Launch | Focus/Attach | Tile by tag |
|---|---|---|---|
| Terminal.app | yes | yes | yes |
| iTerm2 | yes | yes | yes |
| Warp | yes | activate | frontmost |
| Ghostty | yes | activate | frontmost |
| Kitty | yes | activate | frontmost |
| Alacritty | yes | activate | frontmost |
"yes" means full AppleScript-based window matching by title tag. "activate" means the app is brought to front but a specific window can't be targeted. "frontmost" means tiling applies to whatever window is in front.
Screen OCR
See Screen OCR for full details on configuration, scanning, search, and agent usage.
The app reads text from visible windows using Apple's Vision framework and stores results in a local SQLite database with FTS5 full-text search. Agents can use this to "see" what's on screen.
How it works
- Every 60 seconds, the app captures the top visible windows as images
- A SHA256 hash detects whether the window content has changed
- Changed windows are processed through
VNRecognizeTextRequest(fast mode) - Results are stored in
~/.lattices/ocr.dbwith full-text indexing - Entries older than 3 days are automatically purged
Desktop Inventory integration
Desktop Inventory and omni search use OCR to find windows by their content — not just by title or app name. Matching windows can include contextual text snippets. Hyper+G's optional drawer is a compact roster for the current display; use Desktop Inventory or omni search when the on-screen text is the target.
API access
Agents can query OCR data through four API methods:
| Method | Description |
|---|---|
ocr.snapshot |
Current OCR results for all visible windows |
ocr.search |
Full-text search across history (FTS5 syntax) |
ocr.history |
Timeline of OCR results for a specific window |
ocr.scan |
Trigger an immediate scan (bypasses timer) |
import { daemonCall } from '@lattices/lattices'
// Find windows showing error messages
const errors = await daemonCall('ocr.search', { query: 'error OR failed' })
// Read what's currently on screen
const snapshot = await daemonCall('ocr.snapshot')More in the Agent API reference.
Requirements
- Screen Recording permission — required to capture window and display images
- Granted via System Settings > Privacy & Security > Screen Recording
Local services
The menu bar process hosts its services on private Unix sockets in
~/.lattices/run (see LatticesLocalEndpoints). LATTICES_RUNTIME_DIR
moves that directory for isolated tests.
| Service | Socket | Credential |
|---|---|---|
| Agent API / daemon | lattices.sock |
lattices.token |
| Companion bridge | companion.sock |
lattices.token |
| Voice runtime | voice.sock |
voice.token |
| Speech | speech.sock |
speech.token |
The sockets open when the app launches and close when it quits. Voice does
not require an external voxd for normal operation.
Each WebSocket service requires its token in the x-lattices-token header
during the upgrade. lats, daemonCall() and the MCP server read the token
for you. Loopback TCP (9399 for the daemon, 9398 for voice, 9397 for
speech) is a compatibility switch, off by default; see
Private IPC and the gateway.
Agent API server
The menu bar app serves the agent API on lattices.sock. It starts when the
app launches and stops when the app quits.
Checking status
lats daemon statusOr programmatically:
import { isDaemonRunning, daemonCall } from '@lattices/lattices'
if (await isDaemonRunning()) {
const status = await daemonCall('daemon.status')
console.log(status) // { uptime, clientCount, version, windowCount, tmuxSessionCount }
}What it provides
- RPC methods for reading windows, sessions, projects, spaces, layers, processes, terminals, and OCR. Also launching/killing sessions, tiling windows, switching layers, and managing tab groups.
- 5 real-time events (
windows.changed,tmux.changed,processes.changed,layer.switched,ocr.scanComplete) broadcast to all connected clients. - Window tracking that monitors the desktop window list and correlates windows to lats sessions via title tags.
- Space awareness, so it knows which macOS Space each window is on.
Security
The socket directory is mode 0700 and the socket and token are mode
0600, so only your user account can connect. The daemon also checks the
connected peer's user id. Any program running as your user can read the
token; this is an account boundary, not isolation between programs. The
daemon is not reachable from the network. Remote access goes through the
separate gateway (lats gateway start --tailnet) and its approved method
grants.
Full method list in the Agent API reference.
Diagnostics
The diagnostics panel shows a timestamped log of window navigation attempts, including which path succeeded or failed. Useful for debugging Screen Recording / Accessibility permission issues.