@lattices/cli 0.4.7 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +8 -6
  2. package/app/Info.plist +13 -2
  3. package/app/Lattices.app/Contents/Info.plist +13 -2
  4. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/app/Sources/AppShell/App.swift +7 -1
  6. package/app/Sources/AppShell/AppDelegate.swift +64 -1
  7. package/app/Sources/AppShell/AppShellView.swift +10 -0
  8. package/app/Sources/AppShell/AppUpdater.swift +216 -4
  9. package/app/Sources/AppShell/CliActionLauncher.swift +2 -2
  10. package/app/Sources/AppShell/MainView.swift +1 -1
  11. package/app/Sources/AppShell/Preferences.swift +29 -1
  12. package/app/Sources/AppShell/SettingsView.swift +576 -61
  13. package/app/Sources/AppShell/SettingsWindow.swift +4 -0
  14. package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +23 -7
  15. package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +35 -0
  16. package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +1 -1
  17. package/app/Sources/Core/Input/KeyboardRemapConfig.swift +69 -0
  18. package/app/Sources/Core/Input/KeyboardRemapController.swift +184 -0
  19. package/app/Sources/Core/Input/KeyboardRemapStore.swift +84 -0
  20. package/app/Sources/Core/Workspace/SessionManager.swift +1 -1
  21. package/app/Sources/Core/Workspace/WorkspaceManager.swift +3 -3
  22. package/bin/lattices-app.ts +11 -0
  23. package/bin/lattices-dev +11 -0
  24. package/bin/lattices.ts +57 -17
  25. package/docs/app.md +30 -2
  26. package/docs/companion-deck.md +29 -0
  27. package/docs/concepts.md +5 -5
  28. package/docs/config.md +34 -9
  29. package/docs/layers.md +1 -1
  30. package/docs/overview.md +1 -1
  31. package/docs/quickstart.md +4 -4
  32. package/package.json +1 -1
package/bin/lattices.ts CHANGED
@@ -45,7 +45,7 @@ function hasTmux(): boolean {
45
45
 
46
46
  /** Commands that require tmux to be installed */
47
47
  const tmuxRequiredCommands = new Set([
48
- "init", "ls", "list", "kill", "rm", "sync", "reconcile",
48
+ "start", "tmux", "init", "ls", "list", "kill", "rm", "sync", "reconcile",
49
49
  "restart", "respawn", "group", "groups", "tab", "status",
50
50
  "inventory", "sessions",
51
51
  ]);
@@ -53,13 +53,9 @@ const tmuxRequiredCommands = new Set([
53
53
  function requireTmux(command: string | undefined): void {
54
54
  if (hasTmux()) return;
55
55
 
56
- const isImplicitCreate = command && !tmuxRequiredCommands.has(command)
57
- && !["search", "s", "focus", "place", "tile", "t", "windows", "window",
58
- "voice", "call", "layer", "layers", "diag", "diagnostics", "scan",
59
- "ocr", "daemon", "dev", "app", "mouse", "assistant",
60
- "help", "-h", "--help"].includes(command);
56
+ if (!command) return;
61
57
 
62
- if (command && !tmuxRequiredCommands.has(command) && !isImplicitCreate) return;
58
+ if (!tmuxRequiredCommands.has(command)) return;
63
59
 
64
60
  console.error(`
65
61
  \x1b[1;31m✘ tmux not found\x1b[0m
@@ -395,9 +391,9 @@ function tabCommand(groupId?: string, tabName?: string): void {
395
391
  // ── Detect dev command ───────────────────────────────────────────────
396
392
 
397
393
  function detectPackageManager(dir: string): string {
398
- if (existsSync(resolve(dir, "pnpm-lock.yaml"))) return "pnpm";
399
394
  if (existsSync(resolve(dir, "bun.lockb")) || existsSync(resolve(dir, "bun.lock")))
400
395
  return "bun";
396
+ if (existsSync(resolve(dir, "pnpm-lock.yaml"))) return "pnpm";
401
397
  if (existsSync(resolve(dir, "yarn.lock"))) return "yarn";
402
398
  return "npm";
403
399
  }
@@ -663,12 +659,12 @@ function defaultPanes(dir: string): PaneConfig[] {
663
659
  const devCmd = detectDevCommand(dir);
664
660
  if (devCmd) {
665
661
  return [
666
- { name: "claude", cmd: "claude", size: 60 },
662
+ { name: "shell", size: 60 },
667
663
  { name: "server", cmd: devCmd },
668
664
  ];
669
665
  }
670
666
  // No dev server detected → single pane
671
- return [{ name: "claude", cmd: "claude" }];
667
+ return [{ name: "shell" }];
672
668
  }
673
669
 
674
670
  function syncSession(): void {
@@ -765,7 +761,7 @@ function restartPane(target?: string): void {
765
761
  // Resolve target to an index
766
762
  let idx: number;
767
763
  if (target === undefined || target === null || target === "") {
768
- // Default: first pane (claude)
764
+ // Default: first pane
769
765
  idx = 0;
770
766
  } else if (/^\d+$/.test(target)) {
771
767
  idx = parseInt(target, 10);
@@ -1718,10 +1714,12 @@ Usage:
1718
1714
  }
1719
1715
 
1720
1716
  function printUsage(): void {
1721
- console.log(`lattices — Claude Code + dev server in tmux
1717
+ console.log(`lattices — workspace launcher for tmux, windows, layers, and the menu bar app
1722
1718
 
1723
1719
  Usage:
1724
- lattices Create session (or reattach) for current project
1720
+ lattices Show workspace status and common commands
1721
+ lattices start Start or reattach the current directory's tmux workspace
1722
+ lattices tmux Alias for lattices start
1725
1723
  lattices init Generate .lattices.json config for this project
1726
1724
  lattices ls List active tmux sessions
1727
1725
  lattices status Show managed vs unmanaged session inventory
@@ -1780,7 +1778,7 @@ Config (.lattices.json):
1780
1778
  {
1781
1779
  "ensure": true,
1782
1780
  "panes": [
1783
- { "name": "claude", "cmd": "claude", "size": 60 },
1781
+ { "name": "shell", "size": 60 },
1784
1782
  { "name": "server", "cmd": "pnpm dev" },
1785
1783
  { "name": "tests", "cmd": "pnpm test --watch" }
1786
1784
  ]
@@ -1799,7 +1797,7 @@ Recovery:
1799
1797
 
1800
1798
  lattices restart Kills the process in a pane and re-runs its declared command.
1801
1799
  Accepts a pane name or 0-based index (default: 0 / first pane).
1802
- Examples: lattices restart (restarts "claude")
1800
+ Examples: lattices restart (restarts the first pane)
1803
1801
  lattices restart server (restarts "server" by name)
1804
1802
  lattices restart 1 (restarts pane at index 1)
1805
1803
 
@@ -1809,13 +1807,47 @@ Layouts:
1809
1807
  3+ panes → main-vertical (first pane left, rest stacked right)
1810
1808
 
1811
1809
  ┌────────────────────┐ ┌──────────┬─────────┐ ┌──────────┬─────────┐
1812
- claude │ │ claude │ server │ │ claude │ server │
1810
+ shell │ │ shell │ server │ │ shell │ server │
1813
1811
  │ │ │ (60%) │ (40%) │ │ (60%) ├─────────┤
1814
1812
  └────────────────────┘ └──────────┴─────────┘ │ │ tests │
1815
1813
  └──────────┴─────────┘
1816
1814
  `);
1817
1815
  }
1818
1816
 
1817
+ function printHome(): void {
1818
+ const dir = process.cwd();
1819
+ const sessionName = toSessionName(dir);
1820
+ const config = readConfig(dir);
1821
+ const panes = resolvePanes(dir);
1822
+ const tmuxReady = hasTmux();
1823
+ const sessionRunning = tmuxReady && sessionExists(sessionName);
1824
+ const appRunning = runQuiet("pgrep -x Lattices >/dev/null 2>&1 && echo yes") === "yes";
1825
+
1826
+ console.log(`lattices — let's get you situated
1827
+
1828
+ Current directory:
1829
+ ${dir}
1830
+
1831
+ Workspace:
1832
+ session ${sessionName}
1833
+ config ${config ? ".lattices.json" : "none yet"}
1834
+ panes ${panes.map((p) => p.name || "pane").join(", ")}
1835
+ tmux ${tmuxReady ? (sessionRunning ? "running" : "ready") : "missing"}
1836
+ app ${appRunning ? "running" : "not running"}
1837
+
1838
+ Common commands:
1839
+ lattices start Start or reattach this directory's tmux workspace
1840
+ lattices init Create a .lattices.json for this project
1841
+ lattices app Launch the menu bar app
1842
+ lattices ls List active sessions
1843
+ lattices help Show the full command reference
1844
+ `);
1845
+
1846
+ if (!tmuxReady) {
1847
+ console.log("tmux is not installed. Run: brew install tmux");
1848
+ }
1849
+ }
1850
+
1819
1851
  function initConfig(): void {
1820
1852
  const dir = process.cwd();
1821
1853
  const configPath = resolve(dir, ".lattices.json");
@@ -2154,6 +2186,13 @@ function statusInventory(): void {
2154
2186
  requireTmux(command);
2155
2187
 
2156
2188
  switch (command) {
2189
+ case undefined:
2190
+ printHome();
2191
+ break;
2192
+ case "start":
2193
+ case "tmux":
2194
+ createOrAttach();
2195
+ break;
2157
2196
  case "init":
2158
2197
  initConfig();
2159
2198
  break;
@@ -2295,5 +2334,6 @@ switch (command) {
2295
2334
  printUsage();
2296
2335
  break;
2297
2336
  default:
2298
- createOrAttach();
2337
+ console.log(`Unknown command: ${command}`);
2338
+ console.log("Run `lattices help` for the full command reference.");
2299
2339
  }
package/docs/app.md CHANGED
@@ -95,7 +95,7 @@ For each project found, the app reads:
95
95
  ## Session management
96
96
 
97
97
  The app calls the lattices CLI for session operations. Launch runs
98
- `lattices` in the project directory, Sync runs `lattices sync` to
98
+ `lattices start` in the project directory, Sync runs `lattices sync` to
99
99
  reconcile panes, and Restart runs `lattices restart <pane>` to kill
100
100
  and re-run a pane's process. Detach and Kill call `tmux detach-client`
101
101
  and `tmux kill-session` directly.
@@ -159,7 +159,7 @@ in the AI corner. Configure the model and budget in Settings > AI.
159
159
  ## Settings
160
160
 
161
161
  Open via the command palette or the gear icon in the main view.
162
- The settings window has four tabs:
162
+ The settings window has five tabs:
163
163
 
164
164
  ### General
165
165
 
@@ -169,6 +169,7 @@ The settings window has four tabs:
169
169
  | Mode | `learning` or `auto` (see below) |
170
170
  | Scan Root | Directory to scan for .lattices.json configs (type a path or click Browse) |
171
171
  | Updates | Download the latest release and relaunch the app |
172
+ | Keyboard Remaps | Optional Caps Lock layer that maps hold to Hyper and tap to Escape |
172
173
 
173
174
  **Mode** controls how the app handles session interaction:
174
175
 
@@ -176,6 +177,33 @@ The settings window has four tabs:
176
177
  (helpful while getting used to tmux)
177
178
  - **Auto** — detaches sessions automatically (fewer prompts)
178
179
 
180
+ ### Keyboard Remaps
181
+
182
+ **Keyboard remaps** are enabled by default for the laptop-friendly rule:
183
+
184
+ - hold Caps Lock -> Hyper (`Control` + `Option` + `Shift` + `Command`)
185
+ - tap Caps Lock -> Escape
186
+
187
+ Rules live in `~/.lattices/keyboard-remaps.json`, and the Settings toggle
188
+ can turn the layer off. Keyboard remaps require Accessibility permission
189
+ because they use a local event tap.
190
+
191
+ ### Companion
192
+
193
+ Shows the secure local bridge status, Mac bridge fingerprint, supported
194
+ capability grants, and paired iPad or iPhone devices. The paired-device
195
+ list shows each device fingerprint, last-seen time, and granted
196
+ capabilities. You can refresh the list, revoke an individual device, or
197
+ forget all trusted companions.
198
+
199
+ The local companion bridge and trackpad proxy are off by default for
200
+ privacy. Turn the bridge on in Settings > Companion, or open
201
+ `lattices://companion/enable` to enable the bridge and jump straight to
202
+ Companion settings. `lattices://companion/disable` turns it off again.
203
+
204
+ The trackpad proxy toggle lives here. Paired devices still need the
205
+ `input.trackpad` grant before they can send pointer events.
206
+
179
207
  ### AI
180
208
 
181
209
  | Setting | Default | Description |
@@ -132,11 +132,40 @@ Standalone mode now uses:
132
132
  - per-device key agreement
133
133
  - signed requests with nonce and timestamp checks
134
134
  - encrypted deck payloads for snapshots, actions, and trackpad events
135
+ - pairing-time capability grants, enforced again on every protected route
135
136
 
136
137
  The bridge still keeps `/health`, `/deck/manifest`, and pairing
137
138
  bootstrap lightweight so a new companion can connect and establish trust
138
139
  without an external relay or Tailscale dependency.
139
140
 
141
+ ## Reference Security Pattern
142
+
143
+ The standalone bridge is the reference pattern we should share back to
144
+ Talkie and Scout:
145
+
146
+ 1. Bonjour is discovery only. The TXT record exposes protocol version,
147
+ fingerprint, security mode, and coarse capabilities, but no project
148
+ names, sessions, commands, or tokens.
149
+ 2. Local-network control is opt-in. The bridge is disabled by default;
150
+ users enable it from Companion settings or the local
151
+ `lattices://companion/enable` deep link.
152
+ 3. Pairing is explicit Mac approval. A cryptographic handshake or public
153
+ key exchange proves key possession; it does not automatically grant
154
+ control.
155
+ 4. Trust is scoped. Pairing records store granted capabilities such as
156
+ `deck.read`, `deck.perform`, and `input.trackpad`.
157
+ 5. Every protected request is signed with a timestamp and nonce, then
158
+ checked for replay before the route runs.
159
+ 6. Sensitive payloads are encrypted with per-device key agreement and
160
+ route-bound additional authenticated data.
161
+ 7. Authorization happens after authentication. A trusted device still
162
+ needs the route's required capability before it can read state,
163
+ perform actions, or proxy input.
164
+
165
+ That gives the family of apps one ergonomic model: discover nearby,
166
+ pair once, reconnect quietly, and keep control surfaces capability
167
+ scoped.
168
+
140
169
  ## Initial Action Surface
141
170
 
142
171
  The first deck action IDs are intentionally small and map to existing
package/docs/concepts.md CHANGED
@@ -13,8 +13,8 @@ order: 6
13
13
  | **Agent API** | WebSocket server (`ws://127.0.0.1:9399`) inside the menu bar app. Exposes 35+ RPC methods and 5 real-time events for programmatic control. See the [API reference](/docs/api). |
14
14
  | **Agent** | Any program that calls the agent API autonomously — an AI coding agent, a shell script, a CI pipeline, or a custom tool. |
15
15
  | **Session** | A persistent tmux workspace that lives in the background. Survives terminal crashes, disconnects, and closing your laptop. One session per project. Requires tmux. |
16
- | **Pane** | A single terminal view inside a session. A typical setup has two panes side by side — Claude Code on the left, dev server on the right. Requires tmux. |
17
- | **Attach / Detach** | Attaching connects your terminal to an existing session. Detaching disconnects but keeps the session alive — your dev server keeps running, Claude keeps thinking. Requires tmux. |
16
+ | **Pane** | A single terminal view inside a session. A typical setup has two panes side by side — shell on the left, dev server on the right. Requires tmux. |
17
+ | **Attach / Detach** | Attaching connects your terminal to an existing session. Detaching disconnects but keeps the session alive — your shell and dev server keep running. Requires tmux. |
18
18
  | **Sync / Reconcile** | `lattices sync` brings a running session back in line with its declared config — recreates missing panes, re-applies layout, restores labels, re-runs commands in idle panes. Requires tmux. |
19
19
  | **Ensure / Prefill** | Two modes for restoring exited commands on reattach. **Ensure** auto-reruns the command. **Prefill** types it but waits for you to press Enter. Set via `.lattices.json`. Requires tmux. |
20
20
  | **tmux** | Terminal multiplexer (optional). Provides persistent sessions, pane layouts, and command restoration. Install with `brew install tmux` if you want session management. |
@@ -24,8 +24,8 @@ order: 6
24
24
  1. You create a `.lattices.json` file in your project root (or run `lattices init`)
25
25
  2. The menu bar app discovers the project and adds it to the command palette
26
26
  3. You can tile windows, switch layers, search via OCR, and use the agent API
27
- 4. With tmux installed, `lattices` also creates persistent terminal sessions:
28
- - Each pane gets its command (claude, dev server, tests, etc.)
27
+ 4. With tmux installed, `lattices start` also creates persistent terminal sessions:
28
+ - Each pane gets its command (shell, dev server, tests, etc.)
29
29
  - The session persists in the background until you kill it
30
30
  - You can attach/detach from any terminal at any time
31
31
  - If `ensure` is enabled, exited commands auto-restart on reattach
@@ -94,7 +94,7 @@ and other macOS window managers.
94
94
 
95
95
  ### Ensure/prefill restoration (requires tmux)
96
96
 
97
- When you run `lattices` (no arguments) and a session already exists:
97
+ When you run `lattices start` and a session already exists:
98
98
 
99
99
  1. lattices checks the `ensure` / `prefill` flag in `.lattices.json`
100
100
  2. For each pane, it queries `#{pane_current_command}` via tmux
package/docs/config.md CHANGED
@@ -14,7 +14,7 @@ workspace layout. lattices reads this file when creating a session.
14
14
  ```json
15
15
  {
16
16
  "panes": [
17
- { "name": "claude", "cmd": "claude" },
17
+ { "name": "shell" },
18
18
  { "name": "server", "cmd": "pnpm dev" }
19
19
  ]
20
20
  }
@@ -26,7 +26,7 @@ workspace layout. lattices reads this file when creating a session.
26
26
  {
27
27
  "ensure": true,
28
28
  "panes": [
29
- { "name": "claude", "cmd": "claude", "size": 60 },
29
+ { "name": "shell", "size": 60 },
30
30
  { "name": "server", "cmd": "pnpm dev" },
31
31
  { "name": "tests", "cmd": "pnpm test --watch" }
32
32
  ]
@@ -72,7 +72,7 @@ lattices picks a layout based on how many panes you define:
72
72
 
73
73
  ```
74
74
  ┌──────────┬─────────┐
75
- claude │ server │
75
+ shell │ server │
76
76
  │ (60%) │ (40%) │
77
77
  └──────────┴─────────┘
78
78
  ```
@@ -83,7 +83,7 @@ Horizontal split. First pane on the left, second on the right.
83
83
 
84
84
  ```
85
85
  ┌──────────┬─────────┐
86
- claude │ server │
86
+ shell │ server │
87
87
  │ (60%) ├─────────┤
88
88
  │ │ tests │
89
89
  └──────────┴─────────┘
@@ -96,7 +96,7 @@ on the right.
96
96
 
97
97
  ```
98
98
  ┌──────────┬─────────┐
99
- claude │ server │
99
+ shell │ server │
100
100
  │ (60%) ├─────────┤
101
101
  │ │ tests │
102
102
  │ ├─────────┤
@@ -109,10 +109,10 @@ on the right.
109
109
  If there's no `.lattices.json`, lattices still works. It will:
110
110
 
111
111
  1. Create a 2-pane layout (60/40 split)
112
- 2. Run `claude` in the left pane
113
- 3. Auto-detect your dev command from package.json scripts:
112
+ 2. Open a shell in the left pane
113
+ 3. Auto-detect your dev command from package.json scripts and run it on the right:
114
114
  - Looks for: `dev`, `start`, `serve`, `watch` (in that order)
115
- - Detects package manager: pnpm > bun > yarn > npm
115
+ - Detects package manager: bun > pnpm > yarn > npm
116
116
 
117
117
  ## Creating a config
118
118
 
@@ -124,7 +124,9 @@ Run `lattices init` in your project directory to generate a starter
124
124
 
125
125
  | Command | Description |
126
126
  |----------------------------|--------------------------------------------------|
127
- | `lattices` | Create or attach to session for current project |
127
+ | `lattices` | Show workspace status and common commands |
128
+ | `lattices start` | Create or attach to session for current project |
129
+ | `lattices tmux` | Alias for `lattices start` |
128
130
  | `lattices init` | Generate .lattices.json config for this project |
129
131
  | `lattices ls` | List active sessions (requires tmux) |
130
132
  | `lattices kill [name]` | Kill a session (defaults to current project) |
@@ -161,6 +163,29 @@ Run `lattices init` in your project directory to generate a starter
161
163
  Aliases: `ls`/`list`, `kill`/`rm`, `sync`/`reconcile`,
162
164
  `restart`/`respawn`, `tile`/`t`.
163
165
 
166
+ ## Keyboard remaps
167
+
168
+ The menu bar app can create a lightweight keyboard layer from
169
+ `~/.lattices/keyboard-remaps.json`. The default config is:
170
+
171
+ ```json
172
+ {
173
+ "rules": [
174
+ {
175
+ "enabled": true,
176
+ "from": "caps_lock",
177
+ "id": "caps_lock_hyper_escape",
178
+ "toIfAlone": "escape",
179
+ "toIfHeld": "hyper"
180
+ }
181
+ ]
182
+ }
183
+ ```
184
+
185
+ It is enabled by default and can be turned off from Settings -> General ->
186
+ Keyboard remaps. Hold Caps Lock to send Hyper (`Control` + `Option` +
187
+ `Shift` + `Command`), or tap Caps Lock alone to send Escape.
188
+
164
189
  ## Machine-readable output
165
190
 
166
191
  ### `--json` flag
package/docs/layers.md CHANGED
@@ -49,7 +49,7 @@ to per-project configs.
49
49
  - Session name follows the pattern `lattices-group-<id>` (e.g. `lattices-group-vox`)
50
50
  - 1 group = 1 tmux session. Each tab is a tmux window, and each window
51
51
  gets its own panes from that project's `.lattices.json`
52
- - You can still launch projects independently: `cd vox-ios && lattices`
52
+ - You can still launch projects independently: `cd vox-ios && lattices start`
53
53
  creates its own standalone session as before
54
54
 
55
55
  ### Tab group fields
package/docs/overview.md CHANGED
@@ -57,7 +57,7 @@ and you can reattach anytime.
57
57
  ## Example
58
58
 
59
59
  ```bash
60
- cd ~/my-project && lattices
60
+ cd ~/my-project && lattices start
61
61
  ```
62
62
 
63
63
  Agents get the same control programmatically:
@@ -10,7 +10,7 @@ Four steps to a running workspace.
10
10
 
11
11
  ```bash
12
12
  git clone https://github.com/arach/lattices
13
- cd lattices && npm link
13
+ cd lattices && bun link
14
14
  ```
15
15
 
16
16
  Verify: `lattices help` should print usage info.
@@ -39,7 +39,7 @@ This generates a config like:
39
39
  ```json
40
40
  {
41
41
  "panes": [
42
- { "name": "claude", "cmd": "claude", "size": 60 },
42
+ { "name": "shell", "size": 60 },
43
43
  { "name": "server", "cmd": "bun dev" }
44
44
  ]
45
45
  }
@@ -55,12 +55,12 @@ your pane layout:
55
55
 
56
56
  ```bash
57
57
  brew install tmux
58
- cd ~/your-project && lattices
58
+ cd ~/your-project && lattices start
59
59
  ```
60
60
 
61
61
  This creates a tmux session with your configured panes side by side.
62
62
  The session persists in the background — close your terminal, reopen it,
63
- run `lattices` again, and everything is still there.
63
+ run `lattices start` again, and everything is still there.
64
64
 
65
65
  > **Without tmux**, you still get the menu bar app, command palette,
66
66
  > window tiling, workspace layers, OCR, and the full agent API.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lattices/cli",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Agentic window manager for macOS — programmable workspace, smart layouts, managed tmux sessions, and a 35+-method agent API",
5
5
  "bin": {
6
6
  "lattices": "./bin/lattices.ts",