@lattices/cli 0.3.0 → 0.4.0

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 (95) hide show
  1. package/README.md +85 -9
  2. package/app/Package.swift +8 -1
  3. package/app/Sources/AdvisorLearningStore.swift +90 -0
  4. package/app/Sources/AgentSession.swift +377 -0
  5. package/app/Sources/AppDelegate.swift +44 -12
  6. package/app/Sources/AppShellView.swift +81 -8
  7. package/app/Sources/AudioProvider.swift +386 -0
  8. package/app/Sources/CheatSheetHUD.swift +261 -19
  9. package/app/Sources/DaemonProtocol.swift +13 -0
  10. package/app/Sources/DaemonServer.swift +8 -0
  11. package/app/Sources/DesktopModel.swift +164 -5
  12. package/app/Sources/DesktopModelTypes.swift +2 -0
  13. package/app/Sources/DiagnosticLog.swift +104 -2
  14. package/app/Sources/EventBus.swift +1 -0
  15. package/app/Sources/HUDBottomBar.swift +279 -0
  16. package/app/Sources/HUDController.swift +1158 -0
  17. package/app/Sources/HUDLeftBar.swift +849 -0
  18. package/app/Sources/HUDMinimap.swift +179 -0
  19. package/app/Sources/HUDRightBar.swift +774 -0
  20. package/app/Sources/HUDState.swift +367 -0
  21. package/app/Sources/HUDTopBar.swift +243 -0
  22. package/app/Sources/HandsOffSession.swift +733 -0
  23. package/app/Sources/HomeDashboardView.swift +125 -0
  24. package/app/Sources/HotkeyManager.swift +2 -0
  25. package/app/Sources/HotkeyStore.swift +45 -9
  26. package/app/Sources/IntentEngine.swift +925 -0
  27. package/app/Sources/Intents/CreateLayerIntent.swift +54 -0
  28. package/app/Sources/Intents/DistributeIntent.swift +56 -0
  29. package/app/Sources/Intents/FocusIntent.swift +69 -0
  30. package/app/Sources/Intents/HelpIntent.swift +41 -0
  31. package/app/Sources/Intents/KillIntent.swift +47 -0
  32. package/app/Sources/Intents/LatticeIntent.swift +78 -0
  33. package/app/Sources/Intents/LaunchIntent.swift +67 -0
  34. package/app/Sources/Intents/ListSessionsIntent.swift +32 -0
  35. package/app/Sources/Intents/ListWindowsIntent.swift +30 -0
  36. package/app/Sources/Intents/ScanIntent.swift +52 -0
  37. package/app/Sources/Intents/SearchIntent.swift +190 -0
  38. package/app/Sources/Intents/SwitchLayerIntent.swift +50 -0
  39. package/app/Sources/Intents/TileIntent.swift +61 -0
  40. package/app/Sources/LatticesApi.swift +1235 -30
  41. package/app/Sources/LauncherHUD.swift +348 -0
  42. package/app/Sources/MainView.swift +147 -44
  43. package/app/Sources/OcrModel.swift +34 -1
  44. package/app/Sources/OmniSearchState.swift +99 -102
  45. package/app/Sources/OnboardingView.swift +457 -0
  46. package/app/Sources/PermissionChecker.swift +2 -12
  47. package/app/Sources/PiChatDock.swift +454 -0
  48. package/app/Sources/PiChatSession.swift +815 -0
  49. package/app/Sources/PiWorkspaceView.swift +364 -0
  50. package/app/Sources/PlacementSpec.swift +195 -0
  51. package/app/Sources/Preferences.swift +59 -0
  52. package/app/Sources/ProjectScanner.swift +1 -1
  53. package/app/Sources/ScreenMapState.swift +701 -55
  54. package/app/Sources/ScreenMapView.swift +843 -103
  55. package/app/Sources/ScreenMapWindowController.swift +22 -0
  56. package/app/Sources/SessionLayerStore.swift +285 -0
  57. package/app/Sources/SessionManager.swift +4 -1
  58. package/app/Sources/SettingsView.swift +186 -3
  59. package/app/Sources/Theme.swift +9 -8
  60. package/app/Sources/TmuxModel.swift +7 -0
  61. package/app/Sources/TmuxQuery.swift +27 -3
  62. package/app/Sources/VoiceChatView.swift +192 -0
  63. package/app/Sources/VoiceCommandWindow.swift +1594 -0
  64. package/app/Sources/VoiceIntentResolver.swift +671 -0
  65. package/app/Sources/VoxClient.swift +454 -0
  66. package/app/Sources/WindowTiler.swift +348 -87
  67. package/app/Sources/WorkspaceManager.swift +127 -18
  68. package/bin/client.ts +16 -0
  69. package/bin/{daemon-client.js → daemon-client.ts} +49 -30
  70. package/bin/handsoff-infer.ts +280 -0
  71. package/bin/handsoff-worker.ts +731 -0
  72. package/bin/{lattices-app.js → lattices-app.ts} +67 -32
  73. package/bin/lattices-dev +160 -0
  74. package/bin/{lattices.js → lattices.ts} +600 -137
  75. package/bin/project-twin.ts +645 -0
  76. package/docs/agent-execution-plan.md +562 -0
  77. package/docs/agents.md +142 -0
  78. package/docs/api.md +153 -34
  79. package/docs/app.md +29 -1
  80. package/docs/config.md +5 -1
  81. package/docs/handsoff-test-scenarios.md +84 -0
  82. package/docs/layers.md +20 -20
  83. package/docs/ocr.md +14 -5
  84. package/docs/overview.md +5 -1
  85. package/docs/presentation-execution-review.md +491 -0
  86. package/docs/prompts/hands-off-system.md +374 -0
  87. package/docs/prompts/hands-off-turn.md +30 -0
  88. package/docs/prompts/voice-advisor.md +31 -0
  89. package/docs/prompts/voice-fallback.md +23 -0
  90. package/docs/tiling-reference.md +167 -0
  91. package/docs/twins.md +138 -0
  92. package/docs/voice-command-protocol.md +278 -0
  93. package/docs/voice.md +219 -0
  94. package/package.json +21 -10
  95. package/bin/client.js +0 -4
@@ -1,23 +1,24 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env bun
2
2
 
3
3
  import { execSync, spawn } from "node:child_process";
4
4
  import { existsSync, mkdirSync, chmodSync, createWriteStream } from "node:fs";
5
- import { resolve, dirname } from "node:path";
6
- import { fileURLToPath } from "node:url";
5
+ import { resolve } from "node:path";
7
6
  import { get } from "node:https";
7
+ import type { IncomingMessage } from "node:http";
8
8
 
9
- const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const __dirname = import.meta.dir;
10
10
  const appDir = resolve(__dirname, "../app");
11
11
  const bundlePath = resolve(appDir, "Lattices.app");
12
12
  const binaryDir = resolve(bundlePath, "Contents/MacOS");
13
13
  const binaryPath = resolve(binaryDir, "Lattices");
14
+ const entitlementsPath = resolve(__dirname, "../app/Lattices.entitlements");
14
15
 
15
16
  const REPO = "arach/lattices";
16
17
  const ASSET_NAME = "Lattices-macos-arm64";
17
18
 
18
19
  // ── Helpers ──────────────────────────────────────────────────────────
19
20
 
20
- function isRunning() {
21
+ function isRunning(): boolean {
21
22
  try {
22
23
  execSync("pgrep -x Lattices", { stdio: "pipe" });
23
24
  return true;
@@ -26,7 +27,7 @@ function isRunning() {
26
27
  }
27
28
  }
28
29
 
29
- function quit() {
30
+ function quit(): boolean {
30
31
  try {
31
32
  execSync("pkill -x Lattices", { stdio: "pipe" });
32
33
  // Wait briefly for process to exit
@@ -41,7 +42,7 @@ function quit() {
41
42
  }
42
43
  }
43
44
 
44
- function hasSwift() {
45
+ function hasSwift(): boolean {
45
46
  try {
46
47
  execSync("which swift", { stdio: "pipe" });
47
48
  return true;
@@ -50,7 +51,7 @@ function hasSwift() {
50
51
  }
51
52
  }
52
53
 
53
- function launch(extraArgs = []) {
54
+ function launch(extraArgs: string[] = []): void {
54
55
  if (isRunning()) {
55
56
  console.log("lattices app is already running.");
56
57
  return;
@@ -61,9 +62,45 @@ function launch(extraArgs = []) {
61
62
  console.log("lattices app launched.");
62
63
  }
63
64
 
65
+ function resolveSigningIdentity(): string | null {
66
+ try {
67
+ const identities = execSync("security find-identity -v -p codesigning", { stdio: "pipe" }).toString();
68
+ return identities.match(/"(Developer ID Application:[^"]+)"/)?.[1]
69
+ || identities.match(/"(Apple Development:[^"]+)"/)?.[1]
70
+ || null;
71
+ } catch {
72
+ return null;
73
+ }
74
+ }
75
+
76
+ function signBundle(): void {
77
+ const identity = resolveSigningIdentity();
78
+ const entFlag = existsSync(entitlementsPath) ? ` --entitlements '${entitlementsPath}'` : "";
79
+
80
+ if (identity) {
81
+ console.log(`Signing with: ${identity}`);
82
+ try {
83
+ execSync(
84
+ `codesign --force --sign '${identity}'${entFlag} --identifier com.arach.lattices '${bundlePath}'`,
85
+ { stdio: "pipe" }
86
+ );
87
+ return;
88
+ } catch {
89
+ console.log(`Warning: signing with '${identity}' failed — falling back to ad-hoc.`);
90
+ }
91
+ } else {
92
+ console.log("Warning: no local signing identity found — falling back to ad-hoc.");
93
+ }
94
+
95
+ execSync(
96
+ `codesign --force --sign -${entFlag} --identifier com.arach.lattices '${bundlePath}'`,
97
+ { stdio: "pipe" }
98
+ );
99
+ }
100
+
64
101
  // ── Build from source (current arch only) ────────────────────────────
65
102
 
66
- function buildFromSource() {
103
+ function buildFromSource(): boolean {
67
104
  console.log("Building lattices app from source...");
68
105
  try {
69
106
  execSync("swift build -c release", {
@@ -80,6 +117,12 @@ function buildFromSource() {
80
117
  mkdirSync(binaryDir, { recursive: true });
81
118
  execSync(`cp '${builtPath}' '${binaryPath}'`);
82
119
 
120
+ // Copy Info.plist into bundle
121
+ const plistSrc = resolve(__dirname, "../app/Info.plist");
122
+ if (existsSync(plistSrc)) {
123
+ execSync(`cp '${plistSrc}' '${resolve(bundlePath, "Contents/Info.plist")}'`);
124
+ }
125
+
83
126
  // Copy app icon into bundle
84
127
  const iconSrc = resolve(__dirname, "../assets/AppIcon.icns");
85
128
  const resourcesDir = resolve(bundlePath, "Contents/Resources");
@@ -89,18 +132,10 @@ function buildFromSource() {
89
132
  }
90
133
 
91
134
  // Re-sign the bundle so macOS TCC recognizes a stable identity across rebuilds.
92
- // Without this, each build gets a new ad-hoc signature and permission grants are lost.
135
+ // Prefer a real local signing identity; only fall back to ad-hoc when necessary.
93
136
  try {
94
- // Prefer a real signing identity for stable TCC grants; fall back to ad-hoc with fixed identifier
95
- const identities = execSync("security find-identity -v -p codesigning", { stdio: "pipe" }).toString();
96
- const devId = identities.match(/"(Developer ID Application:[^"]+)"/)?.[1]
97
- || identities.match(/"(Apple Development:[^"]+)"/)?.[1];
98
- const signArg = devId ? `'${devId}'` : "-";
99
- execSync(
100
- `codesign --force --sign ${signArg} --identifier com.arach.lattices '${bundlePath}'`,
101
- { stdio: "pipe" }
102
- );
103
- } catch (e) {
137
+ signBundle();
138
+ } catch {
104
139
  // Non-fatal — app still works, just permissions won't persist across rebuilds
105
140
  console.log("Warning: code signing failed — permissions may not persist across rebuilds.");
106
141
  }
@@ -112,10 +147,10 @@ function buildFromSource() {
112
147
 
113
148
  // ── Download from GitHub releases ────────────────────────────────────
114
149
 
115
- function httpsGet(url) {
150
+ function httpsGet(url: string): Promise<IncomingMessage> {
116
151
  return new Promise((resolve, reject) => {
117
152
  get(url, { headers: { "User-Agent": "lattices" } }, (res) => {
118
- if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
153
+ if (res.statusCode! >= 300 && res.statusCode! < 400 && res.headers.location) {
119
154
  return httpsGet(res.headers.location).then(resolve, reject);
120
155
  }
121
156
  if (res.statusCode !== 200) {
@@ -128,24 +163,24 @@ function httpsGet(url) {
128
163
  });
129
164
  }
130
165
 
131
- async function download() {
166
+ async function download(): Promise<boolean> {
132
167
  console.log("Downloading pre-built binary...");
133
168
 
134
169
  try {
135
170
  const apiUrl = `https://api.github.com/repos/${REPO}/releases/latest`;
136
171
  const apiRes = await httpsGet(apiUrl);
137
- const chunks = [];
138
- for await (const chunk of apiRes) chunks.push(chunk);
172
+ const chunks: Buffer[] = [];
173
+ for await (const chunk of apiRes) chunks.push(chunk as Buffer);
139
174
  const release = JSON.parse(Buffer.concat(chunks).toString());
140
175
 
141
- const asset = release.assets?.find((a) => a.name === ASSET_NAME);
176
+ const asset = release.assets?.find((a: { name: string }) => a.name === ASSET_NAME);
142
177
  if (!asset) throw new Error("Binary not found in release assets");
143
178
 
144
179
  const dlRes = await httpsGet(asset.browser_download_url);
145
180
 
146
181
  mkdirSync(binaryDir, { recursive: true });
147
182
  const ws = createWriteStream(binaryPath);
148
- await new Promise((resolve, reject) => {
183
+ await new Promise<void>((resolve, reject) => {
149
184
  dlRes.pipe(ws);
150
185
  ws.on("finish", resolve);
151
186
  ws.on("error", reject);
@@ -155,14 +190,14 @@ async function download() {
155
190
  console.log("Download complete.");
156
191
  return true;
157
192
  } catch (e) {
158
- console.log(`Download failed: ${e.message}`);
193
+ console.log(`Download failed: ${(e as Error).message}`);
159
194
  return false;
160
195
  }
161
196
  }
162
197
 
163
198
  // ── Commands ─────────────────────────────────────────────────────────
164
199
 
165
- async function ensureBinary() {
200
+ async function ensureBinary(): Promise<void> {
166
201
  if (existsSync(binaryPath)) return;
167
202
 
168
203
  // 1. Try local compile (fast, matches exact system)
@@ -179,15 +214,15 @@ async function ensureBinary() {
179
214
  console.error(
180
215
  "Could not build or download the lattices app.\n" +
181
216
  "Options:\n" +
182
- " Install Xcode CLI tools: xcode-select --install\n" +
183
- " Download manually from: https://github.com/" + REPO + "/releases"
217
+ " \u2022 Install Xcode CLI tools: xcode-select --install\n" +
218
+ " \u2022 Download manually from: https://github.com/" + REPO + "/releases"
184
219
  );
185
220
  process.exit(1);
186
221
  }
187
222
 
188
223
  const cmd = process.argv[2];
189
224
  const flags = process.argv.slice(3);
190
- const launchFlags = [];
225
+ const launchFlags: string[] = [];
191
226
  if (flags.includes("--diagnostics") || flags.includes("-d")) launchFlags.push("--diagnostics");
192
227
  if (flags.includes("--screen-map") || flags.includes("-m")) launchFlags.push("--screen-map");
193
228
 
@@ -0,0 +1,160 @@
1
+ #!/usr/bin/env bash
2
+ # lattices-dev — convenience commands for Lattices development
3
+
4
+ set -euo pipefail
5
+
6
+ SCRIPT_PATH="$(readlink -f "$0" 2>/dev/null || python3 -c "import os,sys; print(os.path.realpath(sys.argv[1]))" "$0")"
7
+ APP_DIR="$(cd "$(dirname "$SCRIPT_PATH")/../app" && pwd)"
8
+ LOG_FILE="$HOME/.lattices/lattices.log"
9
+ BINARY="$APP_DIR/.build/release/Lattices"
10
+ BUNDLE="$APP_DIR/Lattices.app"
11
+ BUNDLE_BIN="$BUNDLE/Contents/MacOS/Lattices"
12
+ ENTITLEMENTS="$APP_DIR/Lattices.entitlements"
13
+
14
+ red() { printf "\033[31m%s\033[0m\n" "$*"; }
15
+ green() { printf "\033[32m%s\033[0m\n" "$*"; }
16
+ dim() { printf "\033[2m%s\033[0m\n" "$*"; }
17
+
18
+ select_sign_identity() {
19
+ local identities identity=""
20
+ identities="$(security find-identity -v -p codesigning 2>/dev/null || true)"
21
+ identity="$(printf '%s\n' "$identities" | sed -n 's/.*"\(Developer ID Application:[^"]*\)".*/\1/p' | head -n 1)"
22
+ if [ -z "$identity" ]; then
23
+ identity="$(printf '%s\n' "$identities" | sed -n 's/.*"\(Apple Development:[^"]*\)".*/\1/p' | head -n 1)"
24
+ fi
25
+ printf '%s' "$identity"
26
+ }
27
+
28
+ sign_bundle() {
29
+ local identity sign_status=0
30
+ local -a ent_flags=()
31
+
32
+ if [ -f "$ENTITLEMENTS" ]; then
33
+ ent_flags=(--entitlements "$ENTITLEMENTS")
34
+ fi
35
+
36
+ identity="$(select_sign_identity)"
37
+ if [ -n "$identity" ]; then
38
+ dim "Signing with: $identity"
39
+ if ! codesign --force --sign "$identity" "${ent_flags[@]}" --identifier com.arach.lattices "$BUNDLE"; then
40
+ red "Signing with '$identity' failed. Falling back to ad-hoc."
41
+ sign_status=1
42
+ fi
43
+ else
44
+ sign_status=1
45
+ fi
46
+
47
+ if [ "$sign_status" -ne 0 ]; then
48
+ dim "No usable signing identity found. Using ad-hoc signature."
49
+ codesign --force --sign - "${ent_flags[@]}" --identifier com.arach.lattices "$BUNDLE"
50
+ fi
51
+ }
52
+
53
+ cmd_build() {
54
+ echo "Building release..."
55
+ cd "$APP_DIR" && swift build -c release
56
+ # Copy into app bundle so it runs with the proper bundle ID
57
+ mkdir -p "$(dirname "$BUNDLE_BIN")" "$BUNDLE/Contents/Resources"
58
+ cp "$BINARY" "$BUNDLE_BIN"
59
+ cp "$APP_DIR/Info.plist" "$BUNDLE/Contents/Info.plist" 2>/dev/null || true
60
+ # Re-sign so TCC permissions persist across rebuilds
61
+ sign_bundle
62
+ green "Build complete."
63
+ }
64
+
65
+ cmd_restart() {
66
+ echo "Restarting Lattices..."
67
+ pkill -x Lattices 2>/dev/null && sleep 1 || true
68
+ cmd_build
69
+ open "$BUNDLE"
70
+ green "Lattices restarted."
71
+ }
72
+
73
+ cmd_quit() {
74
+ if pkill -x Lattices 2>/dev/null; then
75
+ green "Lattices stopped."
76
+ else
77
+ dim "Lattices is not running."
78
+ fi
79
+ }
80
+
81
+ cmd_launch() {
82
+ if pgrep -x Lattices >/dev/null 2>&1; then
83
+ dim "Lattices is already running."
84
+ else
85
+ open "$BUNDLE"
86
+ green "Lattices launched."
87
+ fi
88
+ }
89
+
90
+ cmd_logs() {
91
+ if [ -f "$LOG_FILE" ]; then
92
+ tail -f "$LOG_FILE"
93
+ else
94
+ red "No log file at $LOG_FILE"
95
+ fi
96
+ }
97
+
98
+ cmd_log() {
99
+ # Show last N lines (default 30)
100
+ local n="${1:-30}"
101
+ if [ -f "$LOG_FILE" ]; then
102
+ tail -n "$n" "$LOG_FILE"
103
+ else
104
+ red "No log file at $LOG_FILE"
105
+ fi
106
+ }
107
+
108
+ cmd_clear_logs() {
109
+ if [ -f "$LOG_FILE" ]; then
110
+ > "$LOG_FILE"
111
+ green "Logs cleared."
112
+ else
113
+ dim "No log file to clear."
114
+ fi
115
+ }
116
+
117
+ cmd_status() {
118
+ if pgrep -x Lattices >/dev/null 2>&1; then
119
+ local pid=$(pgrep -x Lattices)
120
+ green "Lattices running (pid $pid)"
121
+ else
122
+ dim "Lattices is not running."
123
+ fi
124
+ if [ -f "$LOG_FILE" ]; then
125
+ local lines=$(wc -l < "$LOG_FILE" | tr -d ' ')
126
+ local size=$(du -h "$LOG_FILE" | cut -f1 | tr -d ' ')
127
+ dim "Log: $lines lines, $size"
128
+ fi
129
+ if [ -f "$HOME/.lattices/advisor-learning.jsonl" ]; then
130
+ local entries=$(wc -l < "$HOME/.lattices/advisor-learning.jsonl" | tr -d ' ')
131
+ dim "Advisor learning: $entries entries"
132
+ fi
133
+ }
134
+
135
+ cmd_help() {
136
+ echo "lattices-dev — Lattices development commands"
137
+ echo ""
138
+ echo " restart Quit + rebuild + relaunch"
139
+ echo " build Build release binary"
140
+ echo " quit Stop the running app"
141
+ echo " launch Start the app (if not running)"
142
+ echo " logs Tail the log file (live)"
143
+ echo " log [N] Show last N log lines (default 30)"
144
+ echo " clear-logs Clear the log file"
145
+ echo " status Show running state and stats"
146
+ echo " help Show this help"
147
+ }
148
+
149
+ case "${1:-help}" in
150
+ restart) cmd_restart ;;
151
+ build) cmd_build ;;
152
+ quit|stop) cmd_quit ;;
153
+ launch|start) cmd_launch ;;
154
+ logs) cmd_logs ;;
155
+ log) cmd_log "${2:-30}" ;;
156
+ clear-logs) cmd_clear_logs ;;
157
+ status) cmd_status ;;
158
+ help|--help|-h) cmd_help ;;
159
+ *) red "Unknown command: $1"; cmd_help; exit 1 ;;
160
+ esac