@lattices/cli 0.3.0 → 0.4.1
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.
- package/README.md +85 -9
- package/app/Info.plist +30 -0
- package/app/Lattices.app/Contents/Info.plist +8 -2
- package/app/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/app/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
- package/app/Lattices.app/Contents/Resources/tap.wav +0 -0
- package/app/Lattices.app/Contents/_CodeSignature/CodeResources +139 -0
- package/app/Lattices.entitlements +15 -0
- package/app/Package.swift +8 -1
- package/app/Resources/tap.wav +0 -0
- package/app/Sources/AdvisorLearningStore.swift +90 -0
- package/app/Sources/AgentSession.swift +377 -0
- package/app/Sources/AppDelegate.swift +45 -12
- package/app/Sources/AppShellView.swift +81 -8
- package/app/Sources/AudioProvider.swift +386 -0
- package/app/Sources/CheatSheetHUD.swift +261 -19
- package/app/Sources/DaemonProtocol.swift +13 -0
- package/app/Sources/DaemonServer.swift +8 -0
- package/app/Sources/DesktopModel.swift +189 -6
- package/app/Sources/DesktopModelTypes.swift +2 -0
- package/app/Sources/DiagnosticLog.swift +104 -2
- package/app/Sources/EventBus.swift +1 -0
- package/app/Sources/HUDBottomBar.swift +279 -0
- package/app/Sources/HUDController.swift +1158 -0
- package/app/Sources/HUDLeftBar.swift +849 -0
- package/app/Sources/HUDMinimap.swift +179 -0
- package/app/Sources/HUDRightBar.swift +774 -0
- package/app/Sources/HUDState.swift +367 -0
- package/app/Sources/HUDTopBar.swift +243 -0
- package/app/Sources/HandsOffSession.swift +802 -0
- package/app/Sources/HomeDashboardView.swift +125 -0
- package/app/Sources/HotkeyManager.swift +2 -0
- package/app/Sources/HotkeyStore.swift +49 -9
- package/app/Sources/IntentEngine.swift +962 -0
- package/app/Sources/Intents/CreateLayerIntent.swift +54 -0
- package/app/Sources/Intents/DistributeIntent.swift +56 -0
- package/app/Sources/Intents/FocusIntent.swift +69 -0
- package/app/Sources/Intents/HelpIntent.swift +41 -0
- package/app/Sources/Intents/KillIntent.swift +47 -0
- package/app/Sources/Intents/LatticeIntent.swift +78 -0
- package/app/Sources/Intents/LaunchIntent.swift +67 -0
- package/app/Sources/Intents/ListSessionsIntent.swift +32 -0
- package/app/Sources/Intents/ListWindowsIntent.swift +30 -0
- package/app/Sources/Intents/ScanIntent.swift +52 -0
- package/app/Sources/Intents/SearchIntent.swift +190 -0
- package/app/Sources/Intents/SwitchLayerIntent.swift +50 -0
- package/app/Sources/Intents/TileIntent.swift +61 -0
- package/app/Sources/LatticesApi.swift +1275 -30
- package/app/Sources/LauncherHUD.swift +348 -0
- package/app/Sources/MainView.swift +147 -44
- package/app/Sources/MouseFinder.swift +222 -0
- package/app/Sources/OcrModel.swift +34 -1
- package/app/Sources/OmniSearchState.swift +99 -102
- package/app/Sources/OnboardingView.swift +457 -0
- package/app/Sources/PermissionChecker.swift +2 -12
- package/app/Sources/PiChatDock.swift +454 -0
- package/app/Sources/PiChatSession.swift +815 -0
- package/app/Sources/PiWorkspaceView.swift +364 -0
- package/app/Sources/PlacementSpec.swift +195 -0
- package/app/Sources/Preferences.swift +59 -0
- package/app/Sources/ProjectScanner.swift +58 -45
- package/app/Sources/ScreenMapState.swift +701 -55
- package/app/Sources/ScreenMapView.swift +843 -103
- package/app/Sources/ScreenMapWindowController.swift +22 -0
- package/app/Sources/SessionLayerStore.swift +285 -0
- package/app/Sources/SessionManager.swift +4 -1
- package/app/Sources/SettingsView.swift +186 -3
- package/app/Sources/Theme.swift +9 -8
- package/app/Sources/TmuxModel.swift +7 -0
- package/app/Sources/TmuxQuery.swift +27 -3
- package/app/Sources/VoiceChatView.swift +192 -0
- package/app/Sources/VoiceCommandWindow.swift +1594 -0
- package/app/Sources/VoiceIntentResolver.swift +671 -0
- package/app/Sources/VoxClient.swift +454 -0
- package/app/Sources/WindowTiler.swift +348 -87
- package/app/Sources/WorkspaceManager.swift +127 -18
- package/app/Tests/StageDragTests.swift +333 -0
- package/app/Tests/StageJoinTests.swift +313 -0
- package/app/Tests/StageManagerTests.swift +280 -0
- package/app/Tests/StageTileTests.swift +353 -0
- package/assets/AppIcon.icns +0 -0
- package/bin/client.ts +16 -0
- package/bin/{daemon-client.js → daemon-client.ts} +49 -30
- package/bin/handsoff-infer.ts +280 -0
- package/bin/handsoff-worker.ts +740 -0
- package/bin/lattices-app.ts +338 -0
- package/bin/lattices-dev +208 -0
- package/bin/{lattices.js → lattices.ts} +777 -140
- package/bin/project-twin.ts +645 -0
- package/docs/agent-execution-plan.md +562 -0
- package/docs/agent-layer-guide.md +207 -0
- package/docs/agents.md +142 -0
- package/docs/api.md +153 -34
- package/docs/app.md +29 -1
- package/docs/config.md +5 -1
- package/docs/handsoff-test-scenarios.md +84 -0
- package/docs/layers.md +20 -20
- package/docs/ocr.md +14 -5
- package/docs/overview.md +5 -1
- package/docs/presentation-execution-review.md +491 -0
- package/docs/prompts/hands-off-system.md +374 -0
- package/docs/prompts/hands-off-turn.md +30 -0
- package/docs/prompts/voice-advisor.md +31 -0
- package/docs/prompts/voice-fallback.md +23 -0
- package/docs/tiling-reference.md +167 -0
- package/docs/twins.md +138 -0
- package/docs/voice-command-protocol.md +278 -0
- package/docs/voice.md +219 -0
- package/package.json +29 -11
- package/bin/client.js +0 -4
- package/bin/lattices-app.js +0 -221
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Hands-off inference script — called by HandsOffSession.swift.
|
|
4
|
+
*
|
|
5
|
+
* Usage: echo '{"transcript":"tile chrome left","snapshot":{...}}' | bun run bin/handsoff-infer.ts
|
|
6
|
+
*
|
|
7
|
+
* Reads JSON from stdin, calls Groq via lib/infer.ts, prints JSON result to stdout.
|
|
8
|
+
* All logging goes to stderr so it doesn't pollute the JSON output.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { inferJSON } from "../lib/infer.ts";
|
|
12
|
+
import { readFileSync } from "fs";
|
|
13
|
+
import { join, dirname } from "path";
|
|
14
|
+
import { homedir } from "os";
|
|
15
|
+
|
|
16
|
+
// ── Read input from stdin ──────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const input = await Bun.stdin.text();
|
|
19
|
+
const req = JSON.parse(input) as {
|
|
20
|
+
transcript: string;
|
|
21
|
+
snapshot: {
|
|
22
|
+
stageManager?: boolean;
|
|
23
|
+
smGrouping?: string;
|
|
24
|
+
activeStage?: Array<{ wid: number; app: string; title: string; frame: string }>;
|
|
25
|
+
stripApps?: string[];
|
|
26
|
+
hiddenApps?: string[];
|
|
27
|
+
currentLayer?: string;
|
|
28
|
+
screen?: string;
|
|
29
|
+
};
|
|
30
|
+
history?: Array<{ role: "user" | "assistant"; content: string }>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ── Load system prompt from file ───────────────────────────────────
|
|
34
|
+
|
|
35
|
+
const promptDir = join(dirname(import.meta.dir), "docs", "prompts");
|
|
36
|
+
let systemPrompt: string;
|
|
37
|
+
try {
|
|
38
|
+
systemPrompt = readFileSync(join(promptDir, "hands-off-system.md"), "utf-8")
|
|
39
|
+
.split("\n")
|
|
40
|
+
.filter((l) => !l.startsWith("# "))
|
|
41
|
+
.join("\n")
|
|
42
|
+
.trim();
|
|
43
|
+
} catch {
|
|
44
|
+
systemPrompt = "You are a workspace assistant. Respond with JSON: {actions, spoken}.";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Replace {{intent_catalog}} with the actual tiling reference
|
|
48
|
+
const intentCatalog = `
|
|
49
|
+
tile_window: Tile a window to a screen position
|
|
50
|
+
Slots:
|
|
51
|
+
position (required): Named position or grid:CxR:C,R syntax.
|
|
52
|
+
Halves: left, right, top, bottom
|
|
53
|
+
Quarters (2x2): top-left, top-right, bottom-left, bottom-right
|
|
54
|
+
Thirds (3x1): left-third, center-third, right-third
|
|
55
|
+
Sixths (3x2): top-left-third, top-center-third, top-right-third, bottom-left-third, bottom-center-third, bottom-right-third
|
|
56
|
+
Fourths (4x1): first-fourth, second-fourth, third-fourth, last-fourth
|
|
57
|
+
Eighths (4x2): top-first-fourth, top-second-fourth, top-third-fourth, top-last-fourth, bottom-first-fourth, bottom-second-fourth, bottom-third-fourth, bottom-last-fourth
|
|
58
|
+
Special: maximize (full screen), center (centered floating)
|
|
59
|
+
Grid syntax: grid:CxR:C,R (e.g. grid:5x3:2,1 = center cell of 5x3 grid)
|
|
60
|
+
app (optional): Target app name — match loosely (e.g. "chrome" matches "Google Chrome")
|
|
61
|
+
wid (optional): Target window ID (from snapshot)
|
|
62
|
+
session (optional): Tmux session name
|
|
63
|
+
If no app/wid/session given, tiles the frontmost window.
|
|
64
|
+
"quarter" = 2x2 cell (top-left etc.), NOT a 4x1 fourth.
|
|
65
|
+
"top quarter" = top-left or top-right (2x2). "top third" = top-left-third (3x2).
|
|
66
|
+
Examples: "tile chrome left" → {intent:"tile_window", slots:{app:"chrome", position:"left"}}
|
|
67
|
+
|
|
68
|
+
focus: Focus a window, app, or session
|
|
69
|
+
Slots:
|
|
70
|
+
app (optional): App name to focus
|
|
71
|
+
session (optional): Session name to focus
|
|
72
|
+
wid (optional): Window ID to focus
|
|
73
|
+
|
|
74
|
+
distribute: Arrange windows in an even grid — with optional app filter and region constraint
|
|
75
|
+
Slots:
|
|
76
|
+
app (optional): Filter to windows of this app (e.g. "iTerm2", "Google Chrome"). Without this, distributes ALL visible windows.
|
|
77
|
+
region (optional): Constrain the grid to a screen region. Uses the same position names as tile_window:
|
|
78
|
+
Halves: left, right, top, bottom
|
|
79
|
+
Quarters: top-left, top-right, bottom-left, bottom-right
|
|
80
|
+
Thirds: left-third, center-third, right-third
|
|
81
|
+
Without this, uses the full screen.
|
|
82
|
+
Examples:
|
|
83
|
+
"grid the terminals on the right" → {intent:"distribute", slots:{app:"iTerm2", region:"right"}}
|
|
84
|
+
"organize my chrome windows in the bottom half" → {intent:"distribute", slots:{app:"Google Chrome", region:"bottom"}}
|
|
85
|
+
"spread everything out" → {intent:"distribute", slots:{}}
|
|
86
|
+
"tile all terminals" → {intent:"distribute", slots:{app:"iTerm2"}}
|
|
87
|
+
|
|
88
|
+
swap: Swap the positions of two windows
|
|
89
|
+
Slots:
|
|
90
|
+
wid_a (required): Window ID of the first window (from snapshot)
|
|
91
|
+
wid_b (required): Window ID of the second window (from snapshot)
|
|
92
|
+
Examples:
|
|
93
|
+
"swap Chrome and iTerm" → {intent:"swap", slots:{wid_a:12345, wid_b:67890}}
|
|
94
|
+
|
|
95
|
+
hide: Hide or minimize a window or app
|
|
96
|
+
Slots:
|
|
97
|
+
app (optional): App name to hide (hides the entire app)
|
|
98
|
+
wid (optional): Window ID to minimize (minimizes just that window)
|
|
99
|
+
Use app to hide all windows of an app. Use wid to minimize a single window.
|
|
100
|
+
Examples:
|
|
101
|
+
"hide Slack" → {intent:"hide", slots:{app:"Slack"}}
|
|
102
|
+
"minimize that" → {intent:"hide", slots:{wid:12345}}
|
|
103
|
+
|
|
104
|
+
highlight: Flash a window's border to identify it visually
|
|
105
|
+
Slots:
|
|
106
|
+
wid (optional): Window ID to highlight (from snapshot)
|
|
107
|
+
app (optional): App name to highlight
|
|
108
|
+
Use when the user asks "which one is that?" or wants to visually identify a window.
|
|
109
|
+
Examples:
|
|
110
|
+
"show me the lattices terminal" → {intent:"highlight", slots:{wid:12345}}
|
|
111
|
+
"which one is Chrome?" → {intent:"highlight", slots:{app:"Google Chrome"}}
|
|
112
|
+
|
|
113
|
+
move_to_display: Move a window to another monitor/display
|
|
114
|
+
Slots:
|
|
115
|
+
display (required): Target display index (0 = main/primary, 1 = second, etc.)
|
|
116
|
+
wid (optional): Window ID to move (from snapshot)
|
|
117
|
+
app (optional): App name to move
|
|
118
|
+
position (optional): Tile position on the target display (e.g. "left", "maximize")
|
|
119
|
+
If no wid/app given, moves the frontmost window.
|
|
120
|
+
Examples:
|
|
121
|
+
"put this on my second monitor" → {intent:"move_to_display", slots:{wid:12345, display:1}}
|
|
122
|
+
"move Chrome to the main screen" → {intent:"move_to_display", slots:{app:"Google Chrome", display:0}}
|
|
123
|
+
"send iTerm to the other monitor, left half" → {intent:"move_to_display", slots:{app:"iTerm2", display:1, position:"left"}}
|
|
124
|
+
|
|
125
|
+
undo: Undo the last window move — restore windows to their previous positions
|
|
126
|
+
No slots needed.
|
|
127
|
+
Examples:
|
|
128
|
+
"put it back" → {intent:"undo"}
|
|
129
|
+
"undo that" → {intent:"undo"}
|
|
130
|
+
|
|
131
|
+
search: Search windows by text
|
|
132
|
+
Slots:
|
|
133
|
+
query (required): Search text
|
|
134
|
+
Examples:
|
|
135
|
+
"find the error message" → {intent:"search", slots:{query:"error"}}
|
|
136
|
+
"find all terminal windows" → {intent:"search", slots:{query:"terminal"}}
|
|
137
|
+
|
|
138
|
+
list_windows: List all visible windows
|
|
139
|
+
No slots needed. Use when the user asks "what's on screen?" or "what windows do I have?"
|
|
140
|
+
|
|
141
|
+
list_sessions: List active terminal sessions
|
|
142
|
+
No slots needed. Use when the user asks "what sessions are running?" or "show my projects."
|
|
143
|
+
|
|
144
|
+
switch_layer: Switch to a workspace layer
|
|
145
|
+
Slots:
|
|
146
|
+
layer (required): Layer name or index
|
|
147
|
+
Examples:
|
|
148
|
+
"switch to the web layer" → {intent:"switch_layer", slots:{layer:"web"}}
|
|
149
|
+
"go to layer 2" → {intent:"switch_layer", slots:{layer:"2"}}
|
|
150
|
+
|
|
151
|
+
create_layer: Save current window arrangement as a named layer
|
|
152
|
+
Slots:
|
|
153
|
+
name (required): Layer name
|
|
154
|
+
Examples:
|
|
155
|
+
"save this layout as review" → {intent:"create_layer", slots:{name:"review"}}
|
|
156
|
+
|
|
157
|
+
launch: Launch a project session
|
|
158
|
+
Slots:
|
|
159
|
+
project (required): Project name or path
|
|
160
|
+
Examples:
|
|
161
|
+
"open my frontend project" → {intent:"launch", slots:{project:"frontend"}}
|
|
162
|
+
"start working on lattices" → {intent:"launch", slots:{project:"lattices"}}
|
|
163
|
+
|
|
164
|
+
kill: Kill a terminal session
|
|
165
|
+
Slots:
|
|
166
|
+
session (required): Session name or project name
|
|
167
|
+
Examples:
|
|
168
|
+
"stop the frontend session" → {intent:"kill", slots:{session:"frontend"}}
|
|
169
|
+
|
|
170
|
+
scan: Trigger an immediate screen text scan (OCR)
|
|
171
|
+
No slots needed. Use when the user asks you to read or scan screen content.
|
|
172
|
+
|
|
173
|
+
CHOOSING THE RIGHT INTENT:
|
|
174
|
+
Positioning:
|
|
175
|
+
tile_window = position ONE specific window at a specific spot. Use for 1-6 named windows.
|
|
176
|
+
distribute = auto-grid MANY windows. Use when the user says "all", "my terminals", "everything", or names more windows than the 6-action limit.
|
|
177
|
+
distribute with app+region is the most powerful combo: "grid my terminals on the right" → distribute(app:"iTerm2", region:"right")
|
|
178
|
+
Rearranging:
|
|
179
|
+
swap = exchange positions of exactly two windows. "swap Chrome and iTerm"
|
|
180
|
+
move_to_display = move a window to a different monitor. "put this on my other screen"
|
|
181
|
+
Visibility:
|
|
182
|
+
hide = hide an app or minimize a window. "hide Slack", "minimize that"
|
|
183
|
+
highlight = flash a window's border to identify it. "which one is the lattices terminal?"
|
|
184
|
+
focus = bring a window to the front. "focus Slack", "show me Chrome"
|
|
185
|
+
Recovery:
|
|
186
|
+
undo = restore previous positions after a move. "put it back", "undo that"
|
|
187
|
+
Information:
|
|
188
|
+
list_windows, list_sessions, search = answer questions about the desktop. NO actions needed for pure questions.
|
|
189
|
+
Session lifecycle:
|
|
190
|
+
launch = start a project session. "open my frontend project"
|
|
191
|
+
kill = stop a session. "kill the API"
|
|
192
|
+
|
|
193
|
+
TILING PRESETS (use multiple tile_window actions):
|
|
194
|
+
"split screen" / "side by side" → left + right
|
|
195
|
+
"thirds" → left-third, center-third, right-third
|
|
196
|
+
"main + sidebar" → main app left (or maximize), others stacked right
|
|
197
|
+
"stack" → top + bottom
|
|
198
|
+
"corners" / "quadrants" → top-left, top-right, bottom-left, bottom-right
|
|
199
|
+
"six-up" / "3 by 2" → 3x2 grid using sixth positions
|
|
200
|
+
"eight-up" / "4 by 2" → 4x2 grid using eighth positions
|
|
201
|
+
|
|
202
|
+
TILING PRESETS (use distribute intent):
|
|
203
|
+
"mosaic" / "grid" / "spread out" → distribute (all windows, full screen)
|
|
204
|
+
"grid the terminals" → distribute with app:"iTerm2"
|
|
205
|
+
"terminals on the right" → distribute with app:"iTerm2", region:"right"
|
|
206
|
+
"organize chrome on the left" → distribute with app:"Google Chrome", region:"left"
|
|
207
|
+
`;
|
|
208
|
+
|
|
209
|
+
systemPrompt = systemPrompt.replace("{{intent_catalog}}", intentCatalog);
|
|
210
|
+
|
|
211
|
+
// ── Build the per-turn message ─────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
let userMessage = `USER: "${req.transcript}"\n\n`;
|
|
214
|
+
userMessage += "--- DESKTOP SNAPSHOT ---\n";
|
|
215
|
+
|
|
216
|
+
const snap = req.snapshot;
|
|
217
|
+
if (snap.stageManager) {
|
|
218
|
+
userMessage += `Stage Manager: ON (grouping: ${snap.smGrouping ?? "all-at-once"})\n\n`;
|
|
219
|
+
userMessage += `Active stage (${snap.activeStage?.length ?? 0} windows):\n`;
|
|
220
|
+
for (const w of snap.activeStage ?? []) {
|
|
221
|
+
userMessage += ` [${w.wid}] ${w.app}: "${w.title}" — ${w.frame}\n`;
|
|
222
|
+
}
|
|
223
|
+
userMessage += `\nStrip: ${snap.stripApps?.join(", ") ?? "none"}\n`;
|
|
224
|
+
userMessage += `Other stages: ${snap.hiddenApps?.join(", ") ?? "none"}\n`;
|
|
225
|
+
} else {
|
|
226
|
+
userMessage += "Stage Manager: OFF\n";
|
|
227
|
+
userMessage += `Visible windows (${snap.activeStage?.length ?? 0}):\n`;
|
|
228
|
+
for (const w of snap.activeStage ?? []) {
|
|
229
|
+
userMessage += ` [${w.wid}] ${w.app}: "${w.title}" — ${w.frame}\n`;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
if (snap.currentLayer) {
|
|
234
|
+
userMessage += `\nCurrent layer: ${snap.currentLayer}\n`;
|
|
235
|
+
}
|
|
236
|
+
if (snap.screen) {
|
|
237
|
+
userMessage += `Screen: ${snap.screen}\n`;
|
|
238
|
+
}
|
|
239
|
+
userMessage += "--- END SNAPSHOT ---\n";
|
|
240
|
+
|
|
241
|
+
// ── Call inference ──────────────────────────────────────────────────
|
|
242
|
+
|
|
243
|
+
const messages = (req.history ?? []).map((h) => ({
|
|
244
|
+
role: h.role as "user" | "assistant",
|
|
245
|
+
content: h.content,
|
|
246
|
+
}));
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const { data, raw } = await inferJSON(userMessage, {
|
|
250
|
+
provider: "groq",
|
|
251
|
+
model: "llama-3.3-70b-versatile",
|
|
252
|
+
system: systemPrompt,
|
|
253
|
+
messages,
|
|
254
|
+
temperature: 0.2,
|
|
255
|
+
maxTokens: 512,
|
|
256
|
+
tag: "hands-off",
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Output result as JSON to stdout
|
|
260
|
+
const output = {
|
|
261
|
+
...data,
|
|
262
|
+
_meta: {
|
|
263
|
+
provider: raw.provider,
|
|
264
|
+
model: raw.model,
|
|
265
|
+
durationMs: raw.durationMs,
|
|
266
|
+
tokens: raw.usage?.totalTokens,
|
|
267
|
+
},
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
console.log(JSON.stringify(output));
|
|
271
|
+
} catch (err: any) {
|
|
272
|
+
console.log(
|
|
273
|
+
JSON.stringify({
|
|
274
|
+
actions: [],
|
|
275
|
+
spoken: "Sorry, I had trouble processing that.",
|
|
276
|
+
_meta: { error: err.message },
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|