@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,338 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import { execSync, spawn } from "node:child_process";
|
|
4
|
+
import { existsSync, mkdirSync, chmodSync, createWriteStream, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { get } from "node:https";
|
|
8
|
+
import type { IncomingMessage } from "node:http";
|
|
9
|
+
|
|
10
|
+
const __dirname = import.meta.dir;
|
|
11
|
+
const appDir = resolve(__dirname, "../app");
|
|
12
|
+
const bundlePath = resolve(appDir, "Lattices.app");
|
|
13
|
+
const binaryDir = resolve(bundlePath, "Contents/MacOS");
|
|
14
|
+
const binaryPath = resolve(binaryDir, "Lattices");
|
|
15
|
+
const entitlementsPath = resolve(__dirname, "../app/Lattices.entitlements");
|
|
16
|
+
const resourcesDir = resolve(bundlePath, "Contents/Resources");
|
|
17
|
+
const iconPath = resolve(__dirname, "../assets/AppIcon.icns");
|
|
18
|
+
const tapSoundPath = resolve(__dirname, "../app/Resources/tap.wav");
|
|
19
|
+
|
|
20
|
+
const REPO = "arach/lattices";
|
|
21
|
+
const RELEASE_APP_ASSET_NAMES = ["Lattices.dmg"];
|
|
22
|
+
const RELEASE_BINARY_ASSET_NAMES = ["Lattices-macos-arm64", "LatticeApp-macos-arm64"];
|
|
23
|
+
type ReleaseAsset = { name: string; browser_download_url: string };
|
|
24
|
+
|
|
25
|
+
// ── Helpers ──────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
function isRunning(): boolean {
|
|
28
|
+
try {
|
|
29
|
+
execSync("pgrep -x Lattices", { stdio: "pipe" });
|
|
30
|
+
return true;
|
|
31
|
+
} catch {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function quit(): boolean {
|
|
37
|
+
try {
|
|
38
|
+
execSync("pkill -x Lattices", { stdio: "pipe" });
|
|
39
|
+
// Wait briefly for process to exit
|
|
40
|
+
try { execSync("sleep 0.5", { stdio: "pipe" }); } catch {}
|
|
41
|
+
// Force kill if still running
|
|
42
|
+
if (isRunning()) {
|
|
43
|
+
execSync("pkill -9 -x Lattices", { stdio: "pipe" });
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function hasSwift(): boolean {
|
|
52
|
+
try {
|
|
53
|
+
execSync("which swift", { stdio: "pipe" });
|
|
54
|
+
return true;
|
|
55
|
+
} catch {
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function packageVersion(): string {
|
|
61
|
+
try {
|
|
62
|
+
const pkg = JSON.parse(readFileSync(resolve(__dirname, "../package.json"), "utf8"));
|
|
63
|
+
return typeof pkg.version === "string" ? pkg.version : "0.1.0";
|
|
64
|
+
} catch {
|
|
65
|
+
return "0.1.0";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function launch(extraArgs: string[] = []): void {
|
|
70
|
+
if (isRunning()) {
|
|
71
|
+
console.log("lattices app is already running.");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const args = [bundlePath];
|
|
75
|
+
if (extraArgs.length) args.push("--args", ...extraArgs);
|
|
76
|
+
spawn("open", args, { detached: true, stdio: "ignore" }).unref();
|
|
77
|
+
console.log("lattices app launched.");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function resolveSigningIdentity(): string | null {
|
|
81
|
+
try {
|
|
82
|
+
const identities = execSync("security find-identity -v -p codesigning", { stdio: "pipe" }).toString();
|
|
83
|
+
return identities.match(/"(Developer ID Application:[^"]+)"/)?.[1]
|
|
84
|
+
|| identities.match(/"(Apple Development:[^"]+)"/)?.[1]
|
|
85
|
+
|| null;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function signBundle(): void {
|
|
92
|
+
const identity = resolveSigningIdentity();
|
|
93
|
+
const entFlag = existsSync(entitlementsPath) ? ` --entitlements '${entitlementsPath}'` : "";
|
|
94
|
+
|
|
95
|
+
if (identity) {
|
|
96
|
+
console.log(`Signing with: ${identity}`);
|
|
97
|
+
try {
|
|
98
|
+
execSync(
|
|
99
|
+
`codesign --force --sign '${identity}'${entFlag} --identifier com.arach.lattices '${bundlePath}'`,
|
|
100
|
+
{ stdio: "pipe" }
|
|
101
|
+
);
|
|
102
|
+
return;
|
|
103
|
+
} catch {
|
|
104
|
+
console.log(`Warning: signing with '${identity}' failed — falling back to ad-hoc.`);
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.log("Warning: no local signing identity found — falling back to ad-hoc.");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
execSync(
|
|
111
|
+
`codesign --force --sign -${entFlag} --identifier com.arach.lattices '${bundlePath}'`,
|
|
112
|
+
{ stdio: "pipe" }
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function writeInfoPlist(): void {
|
|
117
|
+
mkdirSync(resolve(bundlePath, "Contents"), { recursive: true });
|
|
118
|
+
const version = packageVersion();
|
|
119
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
120
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
121
|
+
<plist version="1.0">
|
|
122
|
+
<dict>
|
|
123
|
+
<key>CFBundleIdentifier</key>
|
|
124
|
+
<string>com.arach.lattices</string>
|
|
125
|
+
<key>CFBundleName</key>
|
|
126
|
+
<string>Lattices</string>
|
|
127
|
+
<key>CFBundleDisplayName</key>
|
|
128
|
+
<string>Lattices</string>
|
|
129
|
+
<key>CFBundleExecutable</key>
|
|
130
|
+
<string>Lattices</string>
|
|
131
|
+
<key>CFBundleIconFile</key>
|
|
132
|
+
<string>AppIcon</string>
|
|
133
|
+
<key>CFBundlePackageType</key>
|
|
134
|
+
<string>APPL</string>
|
|
135
|
+
<key>CFBundleVersion</key>
|
|
136
|
+
<string>${version}</string>
|
|
137
|
+
<key>CFBundleShortVersionString</key>
|
|
138
|
+
<string>${version}</string>
|
|
139
|
+
<key>LSMinimumSystemVersion</key>
|
|
140
|
+
<string>13.0</string>
|
|
141
|
+
<key>LSUIElement</key>
|
|
142
|
+
<true/>
|
|
143
|
+
<key>NSHighResolutionCapable</key>
|
|
144
|
+
<true/>
|
|
145
|
+
<key>NSSupportsAutomaticTermination</key>
|
|
146
|
+
<true/>
|
|
147
|
+
</dict>
|
|
148
|
+
</plist>
|
|
149
|
+
`;
|
|
150
|
+
writeFileSync(resolve(bundlePath, "Contents/Info.plist"), plist);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function syncBundleResources(): void {
|
|
154
|
+
mkdirSync(resourcesDir, { recursive: true });
|
|
155
|
+
if (existsSync(iconPath)) {
|
|
156
|
+
execSync(`cp '${iconPath}' '${resolve(resourcesDir, "AppIcon.icns")}'`);
|
|
157
|
+
}
|
|
158
|
+
if (existsSync(tapSoundPath)) {
|
|
159
|
+
execSync(`cp '${tapSoundPath}' '${resolve(resourcesDir, "tap.wav")}'`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ── Build from source (current arch only) ────────────────────────────
|
|
164
|
+
|
|
165
|
+
function buildFromSource(): boolean {
|
|
166
|
+
console.log("Building lattices app from source...");
|
|
167
|
+
try {
|
|
168
|
+
execSync("swift build -c release", {
|
|
169
|
+
cwd: appDir,
|
|
170
|
+
stdio: "inherit",
|
|
171
|
+
});
|
|
172
|
+
} catch {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const builtPath = resolve(appDir, ".build/release/Lattices");
|
|
177
|
+
if (!existsSync(builtPath)) return false;
|
|
178
|
+
|
|
179
|
+
mkdirSync(binaryDir, { recursive: true });
|
|
180
|
+
execSync(`cp '${builtPath}' '${binaryPath}'`);
|
|
181
|
+
writeInfoPlist();
|
|
182
|
+
syncBundleResources();
|
|
183
|
+
|
|
184
|
+
// Re-sign the bundle so macOS TCC recognizes a stable identity across rebuilds.
|
|
185
|
+
// Prefer a real local signing identity; only fall back to ad-hoc when necessary.
|
|
186
|
+
try {
|
|
187
|
+
signBundle();
|
|
188
|
+
} catch {
|
|
189
|
+
// Non-fatal — app still works, just permissions won't persist across rebuilds
|
|
190
|
+
console.log("Warning: code signing failed — permissions may not persist across rebuilds.");
|
|
191
|
+
}
|
|
192
|
+
// Update bundle timestamp so Finder shows the correct modified date
|
|
193
|
+
try { execSync(`touch '${bundlePath}'`, { stdio: "pipe" }); } catch {}
|
|
194
|
+
console.log("Build complete.");
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ── Download from GitHub releases ────────────────────────────────────
|
|
199
|
+
|
|
200
|
+
function httpsGet(url: string): Promise<IncomingMessage> {
|
|
201
|
+
return new Promise((resolve, reject) => {
|
|
202
|
+
get(url, { headers: { "User-Agent": "lattices" } }, (res) => {
|
|
203
|
+
if (res.statusCode! >= 300 && res.statusCode! < 400 && res.headers.location) {
|
|
204
|
+
return httpsGet(res.headers.location).then(resolve, reject);
|
|
205
|
+
}
|
|
206
|
+
if (res.statusCode !== 200) {
|
|
207
|
+
reject(new Error(`HTTP ${res.statusCode}`));
|
|
208
|
+
res.resume();
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
resolve(res);
|
|
212
|
+
}).on("error", reject);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function downloadToFile(url: string, destination: string): Promise<void> {
|
|
217
|
+
const res = await httpsGet(url);
|
|
218
|
+
const ws = createWriteStream(destination);
|
|
219
|
+
await new Promise<void>((resolve, reject) => {
|
|
220
|
+
res.pipe(ws);
|
|
221
|
+
ws.on("finish", resolve);
|
|
222
|
+
ws.on("error", reject);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function installBundleFromDmg(dmgPath: string): void {
|
|
227
|
+
const mountPoint = mkdtempSync(join(tmpdir(), "lattices-mount-"));
|
|
228
|
+
try {
|
|
229
|
+
execSync(`hdiutil attach -nobrowse -readonly -mountpoint '${mountPoint}' '${dmgPath}'`, { stdio: "pipe" });
|
|
230
|
+
const mountedBundle = resolve(mountPoint, "Lattices.app");
|
|
231
|
+
if (!existsSync(mountedBundle)) {
|
|
232
|
+
throw new Error("Lattices.app not found in mounted disk image");
|
|
233
|
+
}
|
|
234
|
+
rmSync(bundlePath, { recursive: true, force: true });
|
|
235
|
+
execSync(`cp -R '${mountedBundle}' '${bundlePath}'`);
|
|
236
|
+
} finally {
|
|
237
|
+
try {
|
|
238
|
+
execSync(`hdiutil detach '${mountPoint}' -quiet`, { stdio: "pipe" });
|
|
239
|
+
} catch {}
|
|
240
|
+
rmSync(mountPoint, { recursive: true, force: true });
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function download(): Promise<boolean> {
|
|
245
|
+
console.log("Downloading pre-built lattices app...");
|
|
246
|
+
|
|
247
|
+
try {
|
|
248
|
+
const apiUrl = `https://api.github.com/repos/${REPO}/releases/latest`;
|
|
249
|
+
const apiRes = await httpsGet(apiUrl);
|
|
250
|
+
const chunks: Buffer[] = [];
|
|
251
|
+
for await (const chunk of apiRes) chunks.push(chunk as Buffer);
|
|
252
|
+
const release = JSON.parse(Buffer.concat(chunks).toString());
|
|
253
|
+
|
|
254
|
+
const assets: ReleaseAsset[] = Array.isArray(release.assets) ? release.assets : [];
|
|
255
|
+
const appAsset = assets.find((a) =>
|
|
256
|
+
RELEASE_APP_ASSET_NAMES.includes(a.name) || (a.name.endsWith(".dmg") && a.name.startsWith("Lattices"))
|
|
257
|
+
);
|
|
258
|
+
if (appAsset) {
|
|
259
|
+
const tempDir = mkdtempSync(join(tmpdir(), "lattices-download-"));
|
|
260
|
+
const dmgPath = resolve(tempDir, appAsset.name);
|
|
261
|
+
try {
|
|
262
|
+
await downloadToFile(appAsset.browser_download_url, dmgPath);
|
|
263
|
+
installBundleFromDmg(dmgPath);
|
|
264
|
+
} finally {
|
|
265
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
266
|
+
}
|
|
267
|
+
console.log("Download complete.");
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const binaryAsset = assets.find((a) => RELEASE_BINARY_ASSET_NAMES.includes(a.name));
|
|
272
|
+
if (!binaryAsset) throw new Error("App bundle not found in release assets");
|
|
273
|
+
|
|
274
|
+
mkdirSync(binaryDir, { recursive: true });
|
|
275
|
+
await downloadToFile(binaryAsset.browser_download_url, binaryPath);
|
|
276
|
+
chmodSync(binaryPath, 0o755);
|
|
277
|
+
writeInfoPlist();
|
|
278
|
+
syncBundleResources();
|
|
279
|
+
console.log("Download complete.");
|
|
280
|
+
return true;
|
|
281
|
+
} catch (e) {
|
|
282
|
+
console.log(`Download failed: ${(e as Error).message}`);
|
|
283
|
+
return false;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── Commands ─────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
async function ensureBinary(): Promise<void> {
|
|
290
|
+
if (existsSync(binaryPath)) return;
|
|
291
|
+
|
|
292
|
+
const downloaded = await download();
|
|
293
|
+
if (downloaded) return;
|
|
294
|
+
|
|
295
|
+
console.error(
|
|
296
|
+
"Could not find a bundled lattices app or download one.\n" +
|
|
297
|
+
"Options:\n" +
|
|
298
|
+
" \u2022 Reinstall or update @lattices/cli\n" +
|
|
299
|
+
" \u2022 Developers can build from source with: lattices-app build\n" +
|
|
300
|
+
" \u2022 Download manually from: https://github.com/" + REPO + "/releases"
|
|
301
|
+
);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const cmd = process.argv[2];
|
|
306
|
+
const flags = process.argv.slice(3);
|
|
307
|
+
const launchFlags: string[] = [];
|
|
308
|
+
if (flags.includes("--diagnostics") || flags.includes("-d")) launchFlags.push("--diagnostics");
|
|
309
|
+
if (flags.includes("--screen-map") || flags.includes("-m")) launchFlags.push("--screen-map");
|
|
310
|
+
|
|
311
|
+
if (cmd === "build") {
|
|
312
|
+
if (!hasSwift()) {
|
|
313
|
+
console.error("Swift is required. Install with: xcode-select --install");
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
buildFromSource();
|
|
317
|
+
} else if (cmd === "quit") {
|
|
318
|
+
if (quit()) {
|
|
319
|
+
console.log("lattices app stopped.");
|
|
320
|
+
} else {
|
|
321
|
+
console.log("lattices app is not running.");
|
|
322
|
+
}
|
|
323
|
+
} else if (cmd === "restart") {
|
|
324
|
+
// Quit → rebuild → relaunch
|
|
325
|
+
quit();
|
|
326
|
+
if (!hasSwift()) {
|
|
327
|
+
console.error("Swift is required. Install with: xcode-select --install");
|
|
328
|
+
process.exit(1);
|
|
329
|
+
}
|
|
330
|
+
if (!buildFromSource()) {
|
|
331
|
+
console.error("Build failed.");
|
|
332
|
+
process.exit(1);
|
|
333
|
+
}
|
|
334
|
+
launch(launchFlags);
|
|
335
|
+
} else {
|
|
336
|
+
await ensureBinary();
|
|
337
|
+
launch(launchFlags);
|
|
338
|
+
}
|
package/bin/lattices-dev
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
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
|
+
ROOT="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)"
|
|
9
|
+
LOG_FILE="$HOME/.lattices/lattices.log"
|
|
10
|
+
BINARY="$APP_DIR/.build/release/Lattices"
|
|
11
|
+
BUNDLE="$APP_DIR/Lattices.app"
|
|
12
|
+
BUNDLE_BIN="$BUNDLE/Contents/MacOS/Lattices"
|
|
13
|
+
RESOURCES_DIR="$BUNDLE/Contents/Resources"
|
|
14
|
+
ENTITLEMENTS="$APP_DIR/Lattices.entitlements"
|
|
15
|
+
ICON="$ROOT/assets/AppIcon.icns"
|
|
16
|
+
TAP_SOUND="$APP_DIR/Resources/tap.wav"
|
|
17
|
+
VERSION="$(node -p "require('$ROOT/package.json').version" 2>/dev/null || echo '0.1.0')"
|
|
18
|
+
|
|
19
|
+
red() { printf "\033[31m%s\033[0m\n" "$*"; }
|
|
20
|
+
green() { printf "\033[32m%s\033[0m\n" "$*"; }
|
|
21
|
+
dim() { printf "\033[2m%s\033[0m\n" "$*"; }
|
|
22
|
+
|
|
23
|
+
select_sign_identity() {
|
|
24
|
+
local identities identity=""
|
|
25
|
+
identities="$(security find-identity -v -p codesigning 2>/dev/null || true)"
|
|
26
|
+
identity="$(printf '%s\n' "$identities" | sed -n 's/.*"\(Developer ID Application:[^"]*\)".*/\1/p' | head -n 1)"
|
|
27
|
+
if [ -z "$identity" ]; then
|
|
28
|
+
identity="$(printf '%s\n' "$identities" | sed -n 's/.*"\(Apple Development:[^"]*\)".*/\1/p' | head -n 1)"
|
|
29
|
+
fi
|
|
30
|
+
printf '%s' "$identity"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
sign_bundle() {
|
|
34
|
+
local identity sign_status=0
|
|
35
|
+
local -a ent_flags=()
|
|
36
|
+
|
|
37
|
+
if [ -f "$ENTITLEMENTS" ]; then
|
|
38
|
+
ent_flags=(--entitlements "$ENTITLEMENTS")
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
identity="$(select_sign_identity)"
|
|
42
|
+
if [ -n "$identity" ]; then
|
|
43
|
+
dim "Signing with: $identity"
|
|
44
|
+
if ! codesign --force --sign "$identity" "${ent_flags[@]}" --identifier com.arach.lattices "$BUNDLE"; then
|
|
45
|
+
red "Signing with '$identity' failed. Falling back to ad-hoc."
|
|
46
|
+
sign_status=1
|
|
47
|
+
fi
|
|
48
|
+
else
|
|
49
|
+
sign_status=1
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
if [ "$sign_status" -ne 0 ]; then
|
|
53
|
+
dim "No usable signing identity found. Using ad-hoc signature."
|
|
54
|
+
codesign --force --sign - "${ent_flags[@]}" --identifier com.arach.lattices "$BUNDLE"
|
|
55
|
+
fi
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
write_info_plist() {
|
|
59
|
+
mkdir -p "$BUNDLE/Contents"
|
|
60
|
+
cat > "$BUNDLE/Contents/Info.plist" <<PLIST
|
|
61
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
62
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
63
|
+
<plist version="1.0">
|
|
64
|
+
<dict>
|
|
65
|
+
<key>CFBundleIdentifier</key>
|
|
66
|
+
<string>com.arach.lattices</string>
|
|
67
|
+
<key>CFBundleName</key>
|
|
68
|
+
<string>Lattices</string>
|
|
69
|
+
<key>CFBundleDisplayName</key>
|
|
70
|
+
<string>Lattices</string>
|
|
71
|
+
<key>CFBundleExecutable</key>
|
|
72
|
+
<string>Lattices</string>
|
|
73
|
+
<key>CFBundleIconFile</key>
|
|
74
|
+
<string>AppIcon</string>
|
|
75
|
+
<key>CFBundlePackageType</key>
|
|
76
|
+
<string>APPL</string>
|
|
77
|
+
<key>CFBundleVersion</key>
|
|
78
|
+
<string>$VERSION</string>
|
|
79
|
+
<key>CFBundleShortVersionString</key>
|
|
80
|
+
<string>$VERSION</string>
|
|
81
|
+
<key>LSMinimumSystemVersion</key>
|
|
82
|
+
<string>13.0</string>
|
|
83
|
+
<key>LSUIElement</key>
|
|
84
|
+
<true/>
|
|
85
|
+
<key>NSHighResolutionCapable</key>
|
|
86
|
+
<true/>
|
|
87
|
+
<key>NSSupportsAutomaticTermination</key>
|
|
88
|
+
<true/>
|
|
89
|
+
</dict>
|
|
90
|
+
</plist>
|
|
91
|
+
PLIST
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
cmd_build() {
|
|
95
|
+
echo "Building release..."
|
|
96
|
+
cd "$APP_DIR" && swift build -c release
|
|
97
|
+
# Refresh the bundle so dev builds and published bundles are complete.
|
|
98
|
+
rm -rf "$BUNDLE/Contents/MacOS" "$RESOURCES_DIR"
|
|
99
|
+
mkdir -p "$(dirname "$BUNDLE_BIN")" "$RESOURCES_DIR"
|
|
100
|
+
cp "$BINARY" "$BUNDLE_BIN"
|
|
101
|
+
if [ -f "$ICON" ]; then
|
|
102
|
+
cp "$ICON" "$RESOURCES_DIR/AppIcon.icns"
|
|
103
|
+
fi
|
|
104
|
+
if [ -f "$TAP_SOUND" ]; then
|
|
105
|
+
cp "$TAP_SOUND" "$RESOURCES_DIR/tap.wav"
|
|
106
|
+
fi
|
|
107
|
+
write_info_plist
|
|
108
|
+
# Re-sign so TCC permissions persist across rebuilds
|
|
109
|
+
sign_bundle
|
|
110
|
+
green "Build complete."
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
cmd_restart() {
|
|
114
|
+
echo "Restarting Lattices..."
|
|
115
|
+
pkill -x Lattices 2>/dev/null && sleep 1 || true
|
|
116
|
+
cmd_build
|
|
117
|
+
open "$BUNDLE"
|
|
118
|
+
green "Lattices restarted."
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
cmd_quit() {
|
|
122
|
+
if pkill -x Lattices 2>/dev/null; then
|
|
123
|
+
green "Lattices stopped."
|
|
124
|
+
else
|
|
125
|
+
dim "Lattices is not running."
|
|
126
|
+
fi
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
cmd_launch() {
|
|
130
|
+
if pgrep -x Lattices >/dev/null 2>&1; then
|
|
131
|
+
dim "Lattices is already running."
|
|
132
|
+
else
|
|
133
|
+
open "$BUNDLE"
|
|
134
|
+
green "Lattices launched."
|
|
135
|
+
fi
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
cmd_logs() {
|
|
139
|
+
if [ -f "$LOG_FILE" ]; then
|
|
140
|
+
tail -f "$LOG_FILE"
|
|
141
|
+
else
|
|
142
|
+
red "No log file at $LOG_FILE"
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
cmd_log() {
|
|
147
|
+
# Show last N lines (default 30)
|
|
148
|
+
local n="${1:-30}"
|
|
149
|
+
if [ -f "$LOG_FILE" ]; then
|
|
150
|
+
tail -n "$n" "$LOG_FILE"
|
|
151
|
+
else
|
|
152
|
+
red "No log file at $LOG_FILE"
|
|
153
|
+
fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
cmd_clear_logs() {
|
|
157
|
+
if [ -f "$LOG_FILE" ]; then
|
|
158
|
+
> "$LOG_FILE"
|
|
159
|
+
green "Logs cleared."
|
|
160
|
+
else
|
|
161
|
+
dim "No log file to clear."
|
|
162
|
+
fi
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
cmd_status() {
|
|
166
|
+
if pgrep -x Lattices >/dev/null 2>&1; then
|
|
167
|
+
local pid=$(pgrep -x Lattices)
|
|
168
|
+
green "Lattices running (pid $pid)"
|
|
169
|
+
else
|
|
170
|
+
dim "Lattices is not running."
|
|
171
|
+
fi
|
|
172
|
+
if [ -f "$LOG_FILE" ]; then
|
|
173
|
+
local lines=$(wc -l < "$LOG_FILE" | tr -d ' ')
|
|
174
|
+
local size=$(du -h "$LOG_FILE" | cut -f1 | tr -d ' ')
|
|
175
|
+
dim "Log: $lines lines, $size"
|
|
176
|
+
fi
|
|
177
|
+
if [ -f "$HOME/.lattices/advisor-learning.jsonl" ]; then
|
|
178
|
+
local entries=$(wc -l < "$HOME/.lattices/advisor-learning.jsonl" | tr -d ' ')
|
|
179
|
+
dim "Advisor learning: $entries entries"
|
|
180
|
+
fi
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
cmd_help() {
|
|
184
|
+
echo "lattices-dev — Lattices development commands"
|
|
185
|
+
echo ""
|
|
186
|
+
echo " restart Quit + rebuild + relaunch"
|
|
187
|
+
echo " build Build release binary"
|
|
188
|
+
echo " quit Stop the running app"
|
|
189
|
+
echo " launch Start the app (if not running)"
|
|
190
|
+
echo " logs Tail the log file (live)"
|
|
191
|
+
echo " log [N] Show last N log lines (default 30)"
|
|
192
|
+
echo " clear-logs Clear the log file"
|
|
193
|
+
echo " status Show running state and stats"
|
|
194
|
+
echo " help Show this help"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
case "${1:-help}" in
|
|
198
|
+
restart) cmd_restart ;;
|
|
199
|
+
build) cmd_build ;;
|
|
200
|
+
quit|stop) cmd_quit ;;
|
|
201
|
+
launch|start) cmd_launch ;;
|
|
202
|
+
logs) cmd_logs ;;
|
|
203
|
+
log) cmd_log "${2:-30}" ;;
|
|
204
|
+
clear-logs) cmd_clear_logs ;;
|
|
205
|
+
status) cmd_status ;;
|
|
206
|
+
help|--help|-h) cmd_help ;;
|
|
207
|
+
*) red "Unknown command: $1"; cmd_help; exit 1 ;;
|
|
208
|
+
esac
|