@lattices/cli 0.4.2 → 0.4.6

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 (146) hide show
  1. package/README.md +3 -0
  2. package/app/Info.plist +2 -2
  3. package/app/Lattices.app/Contents/Info.plist +2 -2
  4. package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/app/Package.swift +6 -0
  6. package/app/Sources/AppShell/App.swift +20 -0
  7. package/app/Sources/{AppDelegate.swift → AppShell/AppDelegate.swift} +94 -34
  8. package/app/Sources/{AppShellView.swift → AppShell/AppShellView.swift} +12 -1
  9. package/app/Sources/AppShell/AppUpdater.swift +92 -0
  10. package/app/Sources/AppShell/CliActionLauncher.swift +50 -0
  11. package/app/Sources/{HomeDashboardView.swift → AppShell/HomeDashboardView.swift} +18 -10
  12. package/app/Sources/AppShell/LatticesRuntime.swift +61 -0
  13. package/app/Sources/{MainView.swift → AppShell/MainView.swift} +351 -191
  14. package/app/Sources/{OnboardingView.swift → AppShell/OnboardingView.swift} +30 -16
  15. package/app/Sources/{Preferences.swift → AppShell/Preferences.swift} +78 -0
  16. package/app/Sources/{SettingsView.swift → AppShell/SettingsView.swift} +869 -152
  17. package/app/Sources/{HotkeyStore.swift → Core/Actions/HotkeyStore.swift} +9 -5
  18. package/app/Sources/{IntentEngine.swift → Core/Actions/IntentEngine.swift} +51 -27
  19. package/app/Sources/Core/Actions/IntentSchema.swift +94 -0
  20. package/app/Sources/{Intents → Core/Actions/Intents}/LatticeIntent.swift +0 -25
  21. package/app/Sources/{PaletteCommand.swift → Core/Actions/PaletteCommand.swift} +26 -6
  22. package/app/Sources/{VoiceIntentResolver.swift → Core/Actions/VoiceIntentResolver.swift} +46 -4
  23. package/app/Sources/Core/Companion/CompanionActivityLog.swift +70 -0
  24. package/app/Sources/Core/Companion/CompanionKeyboardController.swift +141 -0
  25. package/app/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +438 -0
  26. package/app/Sources/Core/Companion/LatticesCompanionCockpit.swift +555 -0
  27. package/app/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +594 -0
  28. package/app/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +204 -0
  29. package/app/Sources/Core/Companion/LatticesDeckHost.swift +1463 -0
  30. package/app/Sources/{LatticesApi.swift → Core/Daemon/LatticesApi.swift} +125 -4
  31. package/app/Sources/{AppTypeClassifier.swift → Core/Desktop/AppTypeClassifier.swift} +36 -0
  32. package/app/Sources/{DesktopModel.swift → Core/Desktop/DesktopModel.swift} +6 -8
  33. package/app/Sources/Core/Desktop/MouseFinder.swift +527 -0
  34. package/app/Sources/Core/Desktop/SessionWindowLocator.swift +139 -0
  35. package/app/Sources/Core/Desktop/WindowDragSnapController.swift +628 -0
  36. package/app/Sources/Core/Desktop/WindowPreviewCard.swift +100 -0
  37. package/app/Sources/Core/Desktop/WindowPreviewStore.swift +113 -0
  38. package/app/Sources/Core/Desktop/WindowSelectionStore.swift +76 -0
  39. package/app/Sources/{WindowTiler.swift → Core/Desktop/WindowTiler.swift} +351 -172
  40. package/app/Sources/Core/Input/MouseGestureConfig.swift +364 -0
  41. package/app/Sources/Core/Input/MouseGestureController.swift +1203 -0
  42. package/app/Sources/Core/Input/MouseInputDeviceStore.swift +98 -0
  43. package/app/Sources/Core/Input/MouseInputEventViewer.swift +272 -0
  44. package/app/Sources/Core/Input/MouseShortcutStore.swift +107 -0
  45. package/app/Sources/{CommandModeState.swift → Core/Overlays/CommandMode/CommandModeState.swift} +127 -24
  46. package/app/Sources/{CommandModeView.swift → Core/Overlays/CommandMode/CommandModeView.swift} +492 -79
  47. package/app/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +67 -0
  48. package/app/Sources/{CheatSheetHUD.swift → Core/Overlays/HUD/CheatSheetHUD.swift} +1 -0
  49. package/app/Sources/{HUDRightBar.swift → Core/Overlays/HUD/HUDRightBar.swift} +23 -201
  50. package/app/Sources/{LauncherHUD.swift → Core/Overlays/HUD/LauncherHUD.swift} +12 -26
  51. package/app/Sources/{OmniSearchView.swift → Core/Overlays/OmniSearch/OmniSearchView.swift} +136 -2
  52. package/app/Sources/{OmniSearchWindow.swift → Core/Overlays/OmniSearch/OmniSearchWindow.swift} +21 -32
  53. package/app/Sources/Core/Overlays/OverlayPanelShell.swift +241 -0
  54. package/app/Sources/{ScreenMapState.swift → Core/Overlays/ScreenMap/ScreenMapState.swift} +116 -32
  55. package/app/Sources/{ScreenMapView.swift → Core/Overlays/ScreenMap/ScreenMapView.swift} +510 -524
  56. package/app/Sources/{ScreenMapWindowController.swift → Core/Overlays/ScreenMap/ScreenMapWindowController.swift} +12 -4
  57. package/app/Sources/{VoiceCommandWindow.swift → Core/Overlays/Voice/VoiceCommandWindow.swift} +46 -53
  58. package/app/Sources/Core/Pi/PiAuthNextStepCard.swift +148 -0
  59. package/app/Sources/Core/Pi/PiAuthPromptCard.swift +90 -0
  60. package/app/Sources/{PiChatDock.swift → Core/Pi/PiChatDock.swift} +137 -74
  61. package/app/Sources/{PiChatSession.swift → Core/Pi/PiChatSession.swift} +608 -108
  62. package/app/Sources/Core/Pi/PiInstallCallout.swift +86 -0
  63. package/app/Sources/Core/Pi/PiProviderSetupCallout.swift +99 -0
  64. package/app/Sources/{PiWorkspaceView.swift → Core/Pi/PiWorkspaceView.swift} +174 -77
  65. package/app/Sources/{PermissionChecker.swift → Core/System/PermissionChecker.swift} +76 -2
  66. package/app/Sources/Core/System/SystemTelemetryMonitor.swift +273 -0
  67. package/app/Sources/{HandsOffSession.swift → Core/Voice/HandsOffSession.swift} +15 -4
  68. package/app/Sources/{WorkspaceManager.swift → Core/Workspace/WorkspaceManager.swift} +288 -0
  69. package/bin/assistant-intelligence.ts +874 -0
  70. package/bin/handsoff-infer.ts +16 -209
  71. package/bin/handsoff-worker.ts +45 -258
  72. package/bin/lattices-app.ts +62 -0
  73. package/bin/lattices-dev +4 -0
  74. package/bin/lattices.ts +125 -14
  75. package/docs/agents.md +14 -0
  76. package/docs/api.md +55 -0
  77. package/docs/app.md +3 -0
  78. package/docs/companion-deck.md +180 -0
  79. package/docs/component-extraction-roadmap.md +392 -0
  80. package/docs/config.md +25 -0
  81. package/docs/tiling-reference.md +55 -0
  82. package/docs/voice-error-model.md +73 -0
  83. package/package.json +4 -1
  84. package/app/Sources/App.swift +0 -10
  85. package/app/Sources/CommandPaletteWindow.swift +0 -134
  86. package/app/Sources/MouseFinder.swift +0 -222
  87. /package/app/Sources/{KeyRecorderView.swift → AppShell/KeyRecorderView.swift} +0 -0
  88. /package/app/Sources/{MainWindow.swift → AppShell/MainWindow.swift} +0 -0
  89. /package/app/Sources/{SettingsWindow.swift → AppShell/SettingsWindow.swift} +0 -0
  90. /package/app/Sources/{HotkeyManager.swift → Core/Actions/HotkeyManager.swift} +0 -0
  91. /package/app/Sources/{Intents → Core/Actions/Intents}/CreateLayerIntent.swift +0 -0
  92. /package/app/Sources/{Intents → Core/Actions/Intents}/DistributeIntent.swift +0 -0
  93. /package/app/Sources/{Intents → Core/Actions/Intents}/FocusIntent.swift +0 -0
  94. /package/app/Sources/{Intents → Core/Actions/Intents}/HelpIntent.swift +0 -0
  95. /package/app/Sources/{Intents → Core/Actions/Intents}/KillIntent.swift +0 -0
  96. /package/app/Sources/{Intents → Core/Actions/Intents}/LaunchIntent.swift +0 -0
  97. /package/app/Sources/{Intents → Core/Actions/Intents}/ListSessionsIntent.swift +0 -0
  98. /package/app/Sources/{Intents → Core/Actions/Intents}/ListWindowsIntent.swift +0 -0
  99. /package/app/Sources/{Intents → Core/Actions/Intents}/ScanIntent.swift +0 -0
  100. /package/app/Sources/{Intents → Core/Actions/Intents}/SearchIntent.swift +0 -0
  101. /package/app/Sources/{Intents → Core/Actions/Intents}/SwitchLayerIntent.swift +0 -0
  102. /package/app/Sources/{Intents → Core/Actions/Intents}/TileIntent.swift +0 -0
  103. /package/app/Sources/{DaemonProtocol.swift → Core/Daemon/DaemonProtocol.swift} +0 -0
  104. /package/app/Sources/{DaemonServer.swift → Core/Daemon/DaemonServer.swift} +0 -0
  105. /package/app/Sources/{AccessibilityTextExtractor.swift → Core/Desktop/AccessibilityTextExtractor.swift} +0 -0
  106. /package/app/Sources/{DesktopModelTypes.swift → Core/Desktop/DesktopModelTypes.swift} +0 -0
  107. /package/app/Sources/{InventoryManager.swift → Core/Desktop/InventoryManager.swift} +0 -0
  108. /package/app/Sources/{InventoryPath.swift → Core/Desktop/InventoryPath.swift} +0 -0
  109. /package/app/Sources/{OcrModel.swift → Core/Desktop/OcrModel.swift} +0 -0
  110. /package/app/Sources/{OcrStore.swift → Core/Desktop/OcrStore.swift} +0 -0
  111. /package/app/Sources/{PlacementSpec.swift → Core/Desktop/PlacementSpec.swift} +0 -0
  112. /package/app/Sources/{TilePickerView.swift → Core/Desktop/TilePickerView.swift} +0 -0
  113. /package/app/Sources/{AppWindowShell.swift → Core/Overlays/AppWindowShell.swift} +0 -0
  114. /package/app/Sources/{CommandModeWindow.swift → Core/Overlays/CommandMode/CommandModeWindow.swift} +0 -0
  115. /package/app/Sources/{CommandPaletteView.swift → Core/Overlays/CommandPalette/CommandPaletteView.swift} +0 -0
  116. /package/app/Sources/{HUDBottomBar.swift → Core/Overlays/HUD/HUDBottomBar.swift} +0 -0
  117. /package/app/Sources/{HUDController.swift → Core/Overlays/HUD/HUDController.swift} +0 -0
  118. /package/app/Sources/{HUDLeftBar.swift → Core/Overlays/HUD/HUDLeftBar.swift} +0 -0
  119. /package/app/Sources/{HUDMinimap.swift → Core/Overlays/HUD/HUDMinimap.swift} +0 -0
  120. /package/app/Sources/{HUDState.swift → Core/Overlays/HUD/HUDState.swift} +0 -0
  121. /package/app/Sources/{HUDTopBar.swift → Core/Overlays/HUD/HUDTopBar.swift} +0 -0
  122. /package/app/Sources/{LayerBezel.swift → Core/Overlays/HUD/LayerBezel.swift} +0 -0
  123. /package/app/Sources/{OmniSearchState.swift → Core/Overlays/OmniSearch/OmniSearchState.swift} +0 -0
  124. /package/app/Sources/{DiagnosticLog.swift → Core/System/DiagnosticLog.swift} +0 -0
  125. /package/app/Sources/{EventBus.swift → Core/System/EventBus.swift} +0 -0
  126. /package/app/Sources/{ProcessModel.swift → Core/System/ProcessModel.swift} +0 -0
  127. /package/app/Sources/{ProcessQuery.swift → Core/System/ProcessQuery.swift} +0 -0
  128. /package/app/Sources/{AdvisorLearningStore.swift → Core/Voice/AdvisorLearningStore.swift} +0 -0
  129. /package/app/Sources/{AgentSession.swift → Core/Voice/AgentSession.swift} +0 -0
  130. /package/app/Sources/{AudioProvider.swift → Core/Voice/AudioProvider.swift} +0 -0
  131. /package/app/Sources/{VoiceChatView.swift → Core/Voice/VoiceChatView.swift} +0 -0
  132. /package/app/Sources/{VoxClient.swift → Core/Voice/VoxClient.swift} +0 -0
  133. /package/app/Sources/{Project.swift → Core/Workspace/Project.swift} +0 -0
  134. /package/app/Sources/{ProjectScanner.swift → Core/Workspace/ProjectScanner.swift} +0 -0
  135. /package/app/Sources/{SessionLayerStore.swift → Core/Workspace/SessionLayerStore.swift} +0 -0
  136. /package/app/Sources/{SessionManager.swift → Core/Workspace/SessionManager.swift} +0 -0
  137. /package/app/Sources/{Terminal.swift → Core/Workspace/Terminal/Terminal.swift} +0 -0
  138. /package/app/Sources/{TerminalQuery.swift → Core/Workspace/Terminal/TerminalQuery.swift} +0 -0
  139. /package/app/Sources/{TerminalSynthesizer.swift → Core/Workspace/Terminal/TerminalSynthesizer.swift} +0 -0
  140. /package/app/Sources/{TmuxModel.swift → Core/Workspace/Tmux/TmuxModel.swift} +0 -0
  141. /package/app/Sources/{TmuxQuery.swift → Core/Workspace/Tmux/TmuxQuery.swift} +0 -0
  142. /package/app/Sources/{ActionRow.swift → UI/ActionRow.swift} +0 -0
  143. /package/app/Sources/{OrphanRow.swift → UI/OrphanRow.swift} +0 -0
  144. /package/app/Sources/{ProjectRow.swift → UI/ProjectRow.swift} +0 -0
  145. /package/app/Sources/{TabGroupRow.swift → UI/TabGroupRow.swift} +0 -0
  146. /package/app/Sources/{Theme.swift → UI/Theme.swift} +0 -0
@@ -22,6 +22,7 @@ const REPO = "arach/lattices";
22
22
  const RELEASE_APP_ASSET_NAMES = ["Lattices.dmg"];
23
23
  const RELEASE_BINARY_ASSET_NAMES = ["Lattices-macos-arm64", "LatticeApp-macos-arm64"];
24
24
  type ReleaseAsset = { name: string; browser_download_url: string };
25
+ const selfScriptPath = resolve(__dirname, "lattices-app.ts");
25
26
 
26
27
  // ── Helpers ──────────────────────────────────────────────────────────
27
28
 
@@ -79,6 +80,14 @@ function launch(extraArgs: string[] = []): void {
79
80
  console.log("lattices app launched.");
80
81
  }
81
82
 
83
+ function relaunchIfNeeded(shouldLaunch: boolean, extraArgs: string[] = []): void {
84
+ if (!shouldLaunch) {
85
+ console.log("App updated. Launch with: lattices app");
86
+ return;
87
+ }
88
+ launch(extraArgs);
89
+ }
90
+
82
91
  function resolveSigningIdentity(): string | null {
83
92
  try {
84
93
  const identities = execSync("security find-identity -v -p codesigning", { stdio: "pipe" }).toString();
@@ -93,6 +102,11 @@ function resolveSigningIdentity(): string | null {
93
102
  function signBundle(): void {
94
103
  const identity = resolveSigningIdentity();
95
104
  const entFlag = existsSync(entitlementsPath) ? ` --entitlements '${entitlementsPath}'` : "";
105
+ const tempBinaryPath = `${binaryPath}.cstemp`;
106
+
107
+ try {
108
+ if (existsSync(tempBinaryPath)) rmSync(tempBinaryPath, { force: true });
109
+ } catch {}
96
110
 
97
111
  if (identity) {
98
112
  console.log(`Signing with: ${identity}`);
@@ -113,6 +127,10 @@ function signBundle(): void {
113
127
  `codesign --force --sign -${entFlag} --identifier com.arach.lattices '${bundlePath}'`,
114
128
  { stdio: "pipe" }
115
129
  );
130
+
131
+ try {
132
+ if (existsSync(tempBinaryPath)) rmSync(tempBinaryPath, { force: true });
133
+ } catch {}
116
134
  }
117
135
 
118
136
  function writeInfoPlist(): void {
@@ -304,11 +322,48 @@ async function ensureBinary(): Promise<void> {
304
322
  process.exit(1);
305
323
  }
306
324
 
325
+ function spawnDetachedUpdateWorker(extraArgs: string[] = [], shouldLaunch = false): void {
326
+ const workerArgs = [
327
+ selfScriptPath,
328
+ "update",
329
+ "--worker",
330
+ ...(shouldLaunch ? ["--launch"] : []),
331
+ ...extraArgs,
332
+ ];
333
+ const child = spawn(process.execPath, workerArgs, {
334
+ cwd: cliRoot,
335
+ detached: true,
336
+ stdio: "ignore",
337
+ });
338
+ child.unref();
339
+ }
340
+
341
+ async function updateApp(extraArgs: string[] = [], shouldLaunch = false): Promise<void> {
342
+ const wasRunning = isRunning();
343
+ if (wasRunning) {
344
+ quit();
345
+ }
346
+
347
+ const downloaded = await download();
348
+ if (!downloaded) {
349
+ console.error("Update failed.");
350
+ if (wasRunning || shouldLaunch || extraArgs.length > 0) {
351
+ launch(extraArgs);
352
+ }
353
+ process.exit(1);
354
+ }
355
+
356
+ relaunchIfNeeded(shouldLaunch || wasRunning || extraArgs.length > 0, extraArgs);
357
+ }
358
+
307
359
  const cmd = process.argv[2];
308
360
  const flags = process.argv.slice(3);
309
361
  const launchFlags: string[] = [];
310
362
  if (flags.includes("--diagnostics") || flags.includes("-d")) launchFlags.push("--diagnostics");
311
363
  if (flags.includes("--screen-map") || flags.includes("-m")) launchFlags.push("--screen-map");
364
+ const shouldLaunchAfterUpdate = flags.includes("--launch") || launchFlags.length > 0;
365
+ const shouldDetachUpdate = flags.includes("--detach");
366
+ const isUpdateWorker = flags.includes("--worker");
312
367
 
313
368
  if (cmd === "build") {
314
369
  if (!hasSwift()) {
@@ -334,6 +389,13 @@ if (cmd === "build") {
334
389
  process.exit(1);
335
390
  }
336
391
  launch(launchFlags);
392
+ } else if (cmd === "update") {
393
+ if (shouldDetachUpdate && !isUpdateWorker) {
394
+ spawnDetachedUpdateWorker(launchFlags, shouldLaunchAfterUpdate);
395
+ console.log("lattices app update started.");
396
+ } else {
397
+ await updateApp(launchFlags, shouldLaunchAfterUpdate);
398
+ }
337
399
  } else {
338
400
  await ensureBinary();
339
401
  launch(launchFlags);
package/bin/lattices-dev CHANGED
@@ -34,6 +34,8 @@ sign_bundle() {
34
34
  local identity sign_status=0
35
35
  local -a ent_flags=()
36
36
 
37
+ rm -f "$BUNDLE_BIN".cstemp
38
+
37
39
  if [ -f "$ENTITLEMENTS" ]; then
38
40
  ent_flags=(--entitlements "$ENTITLEMENTS")
39
41
  fi
@@ -53,6 +55,8 @@ sign_bundle() {
53
55
  dim "No usable signing identity found. Using ad-hoc signature."
54
56
  codesign --force --sign - "${ent_flags[@]}" --identifier com.arach.lattices "$BUNDLE"
55
57
  fi
58
+
59
+ rm -f "$BUNDLE_BIN".cstemp
56
60
  }
57
61
 
58
62
  write_info_plist() {
package/bin/lattices.ts CHANGED
@@ -47,7 +47,7 @@ function hasTmux(): boolean {
47
47
  const tmuxRequiredCommands = new Set([
48
48
  "init", "ls", "list", "kill", "rm", "sync", "reconcile",
49
49
  "restart", "respawn", "group", "groups", "tab", "status",
50
- "inventory", "distribute", "sessions",
50
+ "inventory", "sessions",
51
51
  ]);
52
52
 
53
53
  function requireTmux(command: string | undefined): void {
@@ -56,7 +56,8 @@ function requireTmux(command: string | undefined): void {
56
56
  const isImplicitCreate = command && !tmuxRequiredCommands.has(command)
57
57
  && !["search", "s", "focus", "place", "tile", "t", "windows", "window",
58
58
  "voice", "call", "layer", "layers", "diag", "diagnostics", "scan",
59
- "ocr", "daemon", "dev", "app", "mouse", "help", "-h", "--help"].includes(command);
59
+ "ocr", "daemon", "dev", "app", "mouse", "assistant",
60
+ "help", "-h", "--help"].includes(command);
60
61
 
61
62
  if (command && !tmuxRequiredCommands.has(command) && !isImplicitCreate) return;
62
63
 
@@ -1182,6 +1183,34 @@ async function voiceCommand(subcommand?: string, ...rest: string[]): Promise<voi
1182
1183
  }
1183
1184
  }
1184
1185
 
1186
+ async function assistantCommand(subcommand?: string, ...rest: string[]): Promise<void> {
1187
+ if (subcommand !== "plan") {
1188
+ console.log("Usage: lattices assistant plan <text> [--json]");
1189
+ return;
1190
+ }
1191
+
1192
+ const jsonOut = rest.includes("--json");
1193
+ const text = rest.filter((arg) => arg !== "--json").join(" ").trim();
1194
+ if (!text) {
1195
+ console.log("Usage: lattices assistant plan <text> [--json]");
1196
+ return;
1197
+ }
1198
+
1199
+ const { tryLocalAssistantPlan } = await import("./assistant-intelligence.ts");
1200
+ const result = tryLocalAssistantPlan(text) ?? {
1201
+ actions: [],
1202
+ spoken: "No local TS plan matched.",
1203
+ _meta: { source: "local-rule", matched: false },
1204
+ };
1205
+
1206
+ if (jsonOut) {
1207
+ console.log(JSON.stringify(result, null, 2));
1208
+ return;
1209
+ }
1210
+
1211
+ console.log(result.spoken);
1212
+ }
1213
+
1185
1214
  async function callCommand(method?: string, ...rest: string[]): Promise<void> {
1186
1215
  if (!method) {
1187
1216
  console.log("Usage: lattices call <method> [params-json]");
@@ -1401,14 +1430,14 @@ async function diagCommand(limit?: string): Promise<void> {
1401
1430
  }
1402
1431
  }
1403
1432
 
1404
- async function distributeCommand(): Promise<void> {
1405
- try {
1406
- const { daemonCall } = await getDaemonClient();
1407
- await daemonCall("space.optimize", { scope: "visible", strategy: "balanced" });
1408
- console.log("Distributed visible windows into grid");
1409
- } catch {
1410
- console.log("Daemon not running. Start with: lattices app");
1411
- }
1433
+ async function distributeCommand(rawArgs: string[] = []): Promise<void> {
1434
+ const request = parseSpaceOptimizeArgs(rawArgs, "visible");
1435
+ await optimizeWindowsCommand(request, "Distributed");
1436
+ }
1437
+
1438
+ async function tileFamilyCommand(rawArgs: string[]): Promise<void> {
1439
+ const request = parseSpaceOptimizeArgs(rawArgs, "active-app");
1440
+ await optimizeWindowsCommand(request, "Smart-tiled");
1412
1441
  }
1413
1442
 
1414
1443
  async function daemonLsCommand(): Promise<boolean> {
@@ -1711,7 +1740,8 @@ Usage:
1711
1740
  lattices windows [--json] List all desktop windows (daemon required)
1712
1741
  lattices sessions [--json] List active tmux sessions via daemon
1713
1742
  lattices tile <position> Tile the frontmost window (left, right, top, etc.)
1714
- lattices distribute Smart-grid all visible windows (daemon required)
1743
+ lattices tile family [app] [region] Smart-grid the frontmost app family, or a named app
1744
+ lattices distribute [app] [region] Smart-grid visible windows or just one app (daemon required)
1715
1745
  lattices layer [name|index] List layers or switch by name/index (daemon required)
1716
1746
  lattices layer create <name> [wid:N ...] [--json '<specs>'] Create a session layer
1717
1747
  lattices layer snap [name] Snapshot visible windows into a session layer
@@ -1721,6 +1751,7 @@ Usage:
1721
1751
  lattices voice status Voice provider status
1722
1752
  lattices voice simulate <t> Parse and execute a voice command
1723
1753
  lattices voice intents List all available intents
1754
+ lattices assistant plan <t> Preview the TS assistant planner
1724
1755
  lattices call <method> [p] Raw daemon API call (params as JSON)
1725
1756
  lattices scan Show text from all visible windows
1726
1757
  lattices scan --full Full text dump
@@ -1737,6 +1768,7 @@ Usage:
1737
1768
  lattices daemon status Show daemon status
1738
1769
  lattices diag [limit] Show diagnostic log entries
1739
1770
  lattices app Launch the menu bar companion app
1771
+ lattices app update Download the latest menu bar app and relaunch
1740
1772
  lattices app build Rebuild the menu bar app
1741
1773
  lattices app restart Rebuild and relaunch the menu bar app
1742
1774
  lattices app quit Stop the menu bar app
@@ -1901,6 +1933,69 @@ const tilePresets: Record<string, (s: ScreenBounds) => number[]> = {
1901
1933
  "right-third": (s) => [s.x + Math.round(s.w * 0.667), s.y, s.x + s.w, s.y + s.h],
1902
1934
  };
1903
1935
 
1936
+ type SpaceOptimizeScope = "visible" | "active-app" | "app";
1937
+
1938
+ interface SpaceOptimizeRequest {
1939
+ scope: SpaceOptimizeScope;
1940
+ app?: string;
1941
+ region?: string;
1942
+ }
1943
+
1944
+ function isPlacementToken(value?: string): boolean {
1945
+ if (!value) return false;
1946
+ const normalized = value.toLowerCase();
1947
+ return normalized in tilePresets || /^grid:\d+x\d+:\d+,\d+$/i.test(normalized);
1948
+ }
1949
+
1950
+ function parseSpaceOptimizeArgs(rawArgs: string[], defaultScope: SpaceOptimizeScope): SpaceOptimizeRequest {
1951
+ const parts = rawArgs.filter(Boolean);
1952
+ if (!parts.length) return { scope: defaultScope };
1953
+
1954
+ const last = parts[parts.length - 1];
1955
+ const region = isPlacementToken(last) ? last : undefined;
1956
+ const appParts = region ? parts.slice(0, -1) : parts;
1957
+ const app = appParts.length ? appParts.join(" ") : undefined;
1958
+
1959
+ if (app) return { scope: "app", app, region };
1960
+ return { scope: defaultScope, region };
1961
+ }
1962
+
1963
+ function formatOptimizeTarget(request: SpaceOptimizeRequest): string {
1964
+ if (request.app) return `"${request.app}"`;
1965
+ return request.scope === "active-app" ? "the frontmost app" : "all visible windows";
1966
+ }
1967
+
1968
+ async function optimizeWindowsCommand(
1969
+ request: SpaceOptimizeRequest,
1970
+ successVerb: string
1971
+ ): Promise<void> {
1972
+ try {
1973
+ const { daemonCall } = await getDaemonClient();
1974
+ const params: Record<string, unknown> = {
1975
+ scope: request.scope,
1976
+ strategy: "balanced",
1977
+ };
1978
+ if (request.app) params.app = request.app;
1979
+ if (request.region) params.region = request.region;
1980
+
1981
+ const result = await daemonCall("space.optimize", params) as any;
1982
+ const count = result?.windowCount ?? 0;
1983
+ const target = formatOptimizeTarget(request);
1984
+ const regionSuffix = request.region ? ` in the ${request.region} region` : "";
1985
+
1986
+ if (count === 0) {
1987
+ console.log(`No eligible windows found for ${target}${regionSuffix}.`);
1988
+ return;
1989
+ }
1990
+
1991
+ console.log(
1992
+ `${successVerb} ${count} window${count === 1 ? "" : "s"} for ${target}${regionSuffix}.`
1993
+ );
1994
+ } catch {
1995
+ console.log("Daemon not running. Start with: lattices app");
1996
+ }
1997
+ }
1998
+
1904
1999
  function tileWindow(position: string): void {
1905
2000
  const preset = tilePresets[position];
1906
2001
  if (!preset) {
@@ -2098,14 +2193,27 @@ switch (command) {
2098
2193
  }
2099
2194
  break;
2100
2195
  case "distribute":
2101
- await distributeCommand();
2196
+ await distributeCommand(args.slice(1));
2102
2197
  break;
2103
2198
  case "tile":
2104
2199
  case "t":
2105
- if (args[1]) {
2200
+ if (args[1] === "family" || args[1] === "app") {
2201
+ await tileFamilyCommand(args.slice(2));
2202
+ } else if (args[1] === "all") {
2203
+ await distributeCommand(args.slice(2));
2204
+ } else if (args[1]) {
2106
2205
  tileWindow(args[1]);
2107
2206
  } else {
2108
- console.log("Usage: lattices tile <position>\n");
2207
+ console.log("Usage:");
2208
+ console.log(" lattices tile <position>");
2209
+ console.log(" lattices tile family [app-name] [region]");
2210
+ console.log(" lattices tile all [app-name] [region]\n");
2211
+ console.log("Examples:");
2212
+ console.log(" lattices tile left");
2213
+ console.log(" lattices tile family");
2214
+ console.log(" lattices tile family right");
2215
+ console.log(" lattices tile family iTerm2");
2216
+ console.log(" lattices tile all Google Chrome left\n");
2109
2217
  console.log("Positions: left, right, top, bottom, top-left, top-right,");
2110
2218
  console.log(" bottom-left, bottom-right, maximize, center,");
2111
2219
  console.log(" left-third, center-third, right-third");
@@ -2141,6 +2249,9 @@ switch (command) {
2141
2249
  case "voice":
2142
2250
  await voiceCommand(args[1], ...args.slice(2));
2143
2251
  break;
2252
+ case "assistant":
2253
+ await assistantCommand(args[1], ...args.slice(2));
2254
+ break;
2144
2255
  case "call":
2145
2256
  await callCommand(args[1], ...args.slice(2));
2146
2257
  break;
package/docs/agents.md CHANGED
@@ -140,3 +140,17 @@ same canonical actions:
140
140
 
141
141
  That keeps the interaction layer flexible while the executor stays
142
142
  predictable.
143
+
144
+ ## Assistant intelligence boundary
145
+
146
+ Assistant planning lives in TypeScript where possible:
147
+
148
+ - `bin/assistant-intelligence.ts` owns the intent catalog, prompt assembly,
149
+ local rule planner, desktop snapshot formatting, and plan normalization.
150
+ - `bin/handsoff-worker.ts` and `bin/handsoff-infer.ts` call that module before
151
+ falling back to model inference.
152
+ - Swift should remain the macOS execution layer: hotkeys, windows, AX/CG,
153
+ SkyLight, panels, and visual feedback.
154
+
155
+ Use `lattices assistant plan <text> --json` to inspect the TS planner without
156
+ launching the app or mutating the desktop.
package/docs/api.md CHANGED
@@ -164,10 +164,65 @@ try {
164
164
 
165
165
  | Method | Type | Description |
166
166
  |--------|------|-------------|
167
+ | `deck.manifest` | read | Shared companion deck manifest |
168
+ | `deck.snapshot` | read | Current companion deck runtime snapshot |
169
+ | `deck.perform` | write | Perform a companion deck action |
167
170
  | `daemon.status` | read | Health check and stats |
168
171
  | `api.schema` | read | Full API schema for self-discovery |
169
172
  | `diagnostics.list` | read | Recent diagnostic entries |
170
173
 
174
+ #### `deck.manifest`
175
+
176
+ Return the shared `DeckKit` manifest exposed by the macOS app. This is
177
+ the contract a future Lattices companion can consume to discover pages,
178
+ capabilities, and security mode.
179
+
180
+ **Params**: none
181
+
182
+ #### `deck.snapshot`
183
+
184
+ Return the current `DeckKit` runtime snapshot for the macOS host.
185
+
186
+ **Params**: none
187
+
188
+ **Returns**: a `DeckRuntimeSnapshot` object containing:
189
+
190
+ - `voice`
191
+ - `desktop`
192
+ - `switcher`
193
+ - `history`
194
+ - `questions`
195
+
196
+ #### `deck.perform`
197
+
198
+ Perform a `DeckKit` action against the running macOS host and return a
199
+ `DeckActionResult`.
200
+
201
+ **Params**:
202
+
203
+ | Field | Type | Required | Description |
204
+ |-------|------|----------|-------------|
205
+ | `pageID` | string | no | Deck page ID |
206
+ | `actionID` | string | yes | Deck action identifier |
207
+ | `payload` | object | no | Deck action payload |
208
+
209
+ Example:
210
+
211
+ ```json
212
+ {
213
+ "id": "1",
214
+ "method": "deck.perform",
215
+ "params": {
216
+ "pageID": "layout",
217
+ "actionID": "layout.optimize",
218
+ "payload": {
219
+ "strategy": "balanced",
220
+ "region": "right"
221
+ }
222
+ }
223
+ }
224
+ ```
225
+
171
226
  #### `daemon.status`
172
227
 
173
228
  Health check and basic stats.
package/docs/app.md CHANGED
@@ -11,6 +11,7 @@ workspace from there.
11
11
 
12
12
  ```bash
13
13
  lattices app # Build (or download) and launch
14
+ lattices app update # Download the latest release and relaunch
14
15
  lattices app build # Rebuild from source
15
16
  lattices app restart # Quit, rebuild, relaunch
16
17
  lattices app quit # Stop the app
@@ -73,6 +74,7 @@ Available when `layers` are configured in `~/.lattices/workspace.json`
73
74
  | Command | Description |
74
75
  |-------------------|------------------------------------------|
75
76
  | Settings | Open preferences (terminal, scan root) |
77
+ | Update Lattices | Download the latest release and relaunch |
76
78
  | Diagnostics | View logs and debug info |
77
79
  | Refresh Projects | Re-scan for .lattices.json configs |
78
80
  | Quit Lattices | Exit the menu bar app |
@@ -166,6 +168,7 @@ The settings window has four tabs:
166
168
  | Terminal | Which terminal to use (auto-detected from installed) |
167
169
  | Mode | `learning` or `auto` (see below) |
168
170
  | Scan Root | Directory to scan for .lattices.json configs (type a path or click Browse) |
171
+ | Updates | Download the latest release and relaunch the app |
169
172
 
170
173
  **Mode** controls how the app handles session interaction:
171
174
 
@@ -0,0 +1,180 @@
1
+ # Companion Deck
2
+
3
+ This document defines the first extraction boundary for the Lattices
4
+ companion deck work.
5
+
6
+ ## Goals
7
+
8
+ - Build the deck architecture in `lattices` first, without modifying
9
+ `talkie`.
10
+ - Treat `talkie` as the donor and reference implementation, not the
11
+ place where the first shared abstraction is born.
12
+ - Let `lattices` own Mac functionality.
13
+ - Let `talkie` continue to own Talkie-specific flows.
14
+ - Keep the transport and UI shell generic enough that both products can
15
+ embed the same deck later.
16
+
17
+ ## Product Ownership
18
+
19
+ ### Lattices owns
20
+
21
+ - Voice agent control
22
+ - Layout and screen state
23
+ - Application, window, tab, and task switching
24
+ - Session and layer switching
25
+ - Desktop questions and agent follow-up
26
+ - Action history and undo-oriented playback
27
+
28
+ ### Talkie owns
29
+
30
+ - Dictation
31
+ - Memo recording
32
+ - Scratchpad and compose flows
33
+ - Capture-specific flows
34
+ - Talkie-branded deck pages and follow-up actions
35
+
36
+ ### Shared deck kit owns
37
+
38
+ - Page model
39
+ - Action model
40
+ - Runtime snapshot model
41
+ - Security mode model
42
+ - App and task switcher model
43
+ - History feed model
44
+ - Generic host protocol
45
+
46
+ ## Security Modes
47
+
48
+ The deck must support two security modes.
49
+
50
+ ### Standalone
51
+
52
+ Used by a future standalone `Lattices Companion`.
53
+
54
+ - Bonjour discovery
55
+ - Local network only
56
+ - No Tailscale or external relay dependency
57
+ - QR or code-based pairing on top of the local network path
58
+ - Per-device keypairs
59
+ - Signed requests with nonce and timestamp protection
60
+ - Local companion gateway with a reduced action surface
61
+
62
+ ### Embedded
63
+
64
+ Used when the deck is embedded inside `talkie`.
65
+
66
+ - Pairing, trust, transport, and signing are delegated to Talkie
67
+ - Lattices focuses only on local functionality and state
68
+ - The Lattices deck host does not need to own remote security in this mode
69
+
70
+ ## First Lattices Companion Scope
71
+
72
+ The first iPad/iPhone companion for Lattices should focus on these pages:
73
+
74
+ 1. Voice
75
+ 2. Layout
76
+ 3. Switch
77
+ 4. History
78
+
79
+ These pages cover the highest-value mobile control loops without pulling
80
+ Talkie-specific concepts into the new product.
81
+
82
+ ## Module Plan
83
+
84
+ ### `swift/DeckKit`
85
+
86
+ Cross-product contract incubated in the Lattices repo first.
87
+
88
+ - Shared deck schema
89
+ - Security mode model
90
+ - Runtime snapshot model
91
+ - Host protocol
92
+
93
+ ### `LatticesDeckHost`
94
+
95
+ Mac-side adapter owned by Lattices.
96
+
97
+ - Publishes deck pages and runtime state
98
+ - Maps deck actions to the existing Lattices desktop APIs
99
+ - Uses the existing desktop model, layout engine, switcher logic, and
100
+ voice agent surfaces
101
+
102
+ ### `Lattices Companion`
103
+
104
+ Future iOS or iPadOS app that consumes `DeckKit` and the Lattices
105
+ companion gateway.
106
+
107
+ ### `TalkieDeckHost`
108
+
109
+ Future Talkie-side adapter that adds Talkie-only pages on top of the
110
+ shared deck shell.
111
+
112
+ ## Current Lattices Milestone
113
+
114
+ The first host-side integration now lives in the Lattices macOS app.
115
+
116
+ - `swift/DeckKit` continues to own the shared manifest, snapshot,
117
+ action, and security contract.
118
+ - `app/Sources/LatticesDeckHost.swift` is the first concrete Mac host.
119
+ - The menu bar app daemon now exposes:
120
+ - `deck.manifest`
121
+ - `deck.snapshot`
122
+ - `deck.perform`
123
+
124
+ That gives the future iPhone/iPad companion a stable local contract
125
+ before transport and pairing are finalized.
126
+
127
+ The current transport now runs as a local network bridge in the macOS
128
+ app with Bonjour discovery on port `5287` (`LATS` on a phone keypad).
129
+ Standalone mode now uses:
130
+
131
+ - local Mac approval for first-time device pairing
132
+ - per-device key agreement
133
+ - signed requests with nonce and timestamp checks
134
+ - encrypted deck payloads for snapshots, actions, and trackpad events
135
+
136
+ The bridge still keeps `/health`, `/deck/manifest`, and pairing
137
+ bootstrap lightweight so a new companion can connect and establish trust
138
+ without an external relay or Tailscale dependency.
139
+
140
+ ## Initial Action Surface
141
+
142
+ The first deck action IDs are intentionally small and map to existing
143
+ desktop behavior:
144
+
145
+ - `voice.toggle`
146
+ - `voice.cancel`
147
+ - `layout.activateLayer`
148
+ - `layout.optimize`
149
+ - `layout.placeFrontmost`
150
+ - `switch.focusItem`
151
+ - `history.undoLast`
152
+
153
+ This keeps the first bridge focused on real Mac control loops instead
154
+ of inventing a second execution stack.
155
+
156
+ ## Rollout Sequence
157
+
158
+ 1. Leave Talkie untouched and use it as the donor reference.
159
+ 2. Incubate `DeckKit` in `lattices`.
160
+ 3. Build a clean Lattices companion around `Voice`, `Layout`,
161
+ `Switch`, and `History`.
162
+ 4. Prove standalone local pairing and strong security for Lattices.
163
+ 5. Harden the deck contract.
164
+ 6. Retrofit the stabilized deck kit back into Talkie.
165
+
166
+ Embedded mode remains a first-class constraint throughout the rollout.
167
+ The standalone bridge must not leak into the shared deck contract in a
168
+ way that would make Talkie embedding awkward later.
169
+
170
+ ## Vox
171
+
172
+ Vox is the preferred voice dependency for the Lattices companion path.
173
+
174
+ - Prefer direct embedding through `VoxCore` and `VoxEngine` for
175
+ in-process ASR and TTS inside Apple apps.
176
+ - Keep `VoxBridge` available as an optional daemon-style path when a
177
+ shared runtime is more appropriate than direct embedding.
178
+ - Keep the first contract in `DeckKit` voice-agnostic.
179
+ - Let `LatticesDeckHost` decide whether voice is served by embedded Vox
180
+ or another local service surface.