@lattices/cli 0.4.14 → 0.6.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.
- package/README.md +5 -7
- package/apps/mac/Info.plist +4 -4
- package/apps/mac/Lattices.app/Contents/Info.plist +4 -12
- package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/bin/lattices-app.ts +110 -17
- package/bin/lattices-build +125 -0
- package/bin/lattices-dev +89 -16
- package/bin/lattices.ts +977 -16
- package/docs/agents.md +81 -4
- package/docs/ai-chat-ux-review.md +416 -0
- package/docs/api.md +135 -3
- package/docs/app.md +30 -8
- package/docs/config.md +4 -0
- package/docs/mouse-gestures.md +60 -1
- package/docs/proposals/LAT-004-interactive-overlay-actors.md +1 -1
- package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
- package/docs/proposals/LAT-006-mira-in-lattices.md +553 -0
- package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
- package/docs/reference/dewey.config.ts +2 -2
- package/docs/release.md +171 -0
- package/docs/repo-structure.md +5 -5
- package/docs/voice.md +11 -27
- package/package.json +11 -10
- package/apps/mac/Package.swift +0 -27
- package/apps/mac/Sources/AppShell/App.swift +0 -26
- package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
- package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
- package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
- package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
- package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
- package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
- package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
- package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
- package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
- package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
- package/apps/mac/Sources/AppShell/MainView.swift +0 -847
- package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
- package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
- package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
- package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
- package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
- package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
- package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
- package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
- package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
- package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
- package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
- package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
- package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
- package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
- package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
- package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
- package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
- package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
- package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
- package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
- package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
- package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
- package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
- package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
- package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
- package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
- package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
- package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
- package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
- package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
- package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
- package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
- package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
- package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
- package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
- package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
- package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
- package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
- package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
- package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
- package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
- package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
- package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
- package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
- package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
- package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
- package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
- package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
- package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
- package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
- package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
- package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
- package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
- package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
- package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
- package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
- package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
- package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
- package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
- package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
- package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
- package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
- package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
- package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2583
- package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
- package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
- package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
- package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
- package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
- package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
- package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
- package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
- package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
- package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
- package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
- package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
- package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
- package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
- package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
- package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
- package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
- package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
- package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
- package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
- package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
- package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
- package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
- package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
- package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
- package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
- package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
- package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
- package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
- package/apps/mac/Sources/Core/System/Capability.swift +0 -79
- package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
- package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
- package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
- package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
- package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
- package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
- package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
- package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
- package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
- package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
- package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
- package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
- package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
- package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
- package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
- package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
- package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
- package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
- package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
- package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
- package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
- package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
- package/apps/mac/Sources/UI/ActionRow.swift +0 -78
- package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
- package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
- package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
- package/apps/mac/Sources/UI/Theme.swift +0 -164
- package/apps/mac/Tests/StageDragTests.swift +0 -333
- package/apps/mac/Tests/StageJoinTests.swift +0 -313
- package/apps/mac/Tests/StageManagerTests.swift +0 -280
- package/apps/mac/Tests/StageTileTests.swift +0 -353
- package/swift/Package.swift +0 -20
- package/swift/Sources/DeckKit/DeckAction.swift +0 -51
- package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
- package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
- package/swift/Sources/DeckKit/DeckHost.swift +0 -7
- package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
- package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
- package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
- package/swift/Sources/DeckKit/DeckValue.swift +0 -93
- package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
- package/swift/Tests/DeckKitTests/DeckKitTests.swift +0 -286
package/bin/lattices.ts
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { createHash } from "node:crypto";
|
|
4
4
|
import { execSync } from "node:child_process";
|
|
5
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
-
import { basename, resolve } from "node:path";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { basename, dirname, isAbsolute, resolve } from "node:path";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
8
|
|
|
9
9
|
// Daemon client (lazy-loaded to avoid blocking startup for TTY commands)
|
|
@@ -95,6 +95,53 @@ function esc(str: string): string {
|
|
|
95
95
|
return str.replace(/'/g, "'\\''");
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
+
function appleScriptString(str: string): string {
|
|
99
|
+
return str.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function slugify(str: string): string {
|
|
103
|
+
return str
|
|
104
|
+
.toLowerCase()
|
|
105
|
+
.replace(/\.app$/i, "")
|
|
106
|
+
.replace(/[^a-z0-9_-]+/g, "-")
|
|
107
|
+
.replace(/^-+|-+$/g, "") || "app";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function parseFlagValue(args: string[], name: string): string | undefined {
|
|
111
|
+
const prefix = `--${name}=`;
|
|
112
|
+
const exact = `--${name}`;
|
|
113
|
+
for (let i = 0; i < args.length; i++) {
|
|
114
|
+
if (args[i].startsWith(prefix)) return args[i].slice(prefix.length);
|
|
115
|
+
if (args[i] === exact) return args[i + 1];
|
|
116
|
+
}
|
|
117
|
+
return undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function hasFlag(args: string[], name: string): boolean {
|
|
121
|
+
return args.includes(`--${name}`);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function nonFlagArgs(args: string[]): string[] {
|
|
125
|
+
const valueFlags = new Set([
|
|
126
|
+
"id", "state", "ttl", "ttlMs", "x", "y", "gap", "placement", "style", "name", "scale",
|
|
127
|
+
"hud-url", "hudUrl", "hud-html", "hudHTML", "hudHtml", "hud-title", "hudTitle",
|
|
128
|
+
"hud-width", "hudWidth", "hud-height", "hudHeight", "width", "height",
|
|
129
|
+
"manifest", "root", "max-depth", "maxDepth", "read-access", "readAccess",
|
|
130
|
+
"pause",
|
|
131
|
+
]);
|
|
132
|
+
const out: string[] = [];
|
|
133
|
+
for (let i = 0; i < args.length; i++) {
|
|
134
|
+
const arg = args[i];
|
|
135
|
+
if (!arg.startsWith("--")) {
|
|
136
|
+
out.push(arg);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const flagName = arg.slice(2);
|
|
140
|
+
if (!arg.includes("=") && valueFlags.has(flagName)) i++;
|
|
141
|
+
}
|
|
142
|
+
return out;
|
|
143
|
+
}
|
|
144
|
+
|
|
98
145
|
// ── Config ───────────────────────────────────────────────────────────
|
|
99
146
|
|
|
100
147
|
function readConfig(dir: string): any | null {
|
|
@@ -559,19 +606,21 @@ function detectProjectType(dir: string): string | null {
|
|
|
559
606
|
return null;
|
|
560
607
|
}
|
|
561
608
|
|
|
609
|
+
async function forwardToLatticesDevHelper(dir: string, cmd: string, extraFlags: string[] = []): Promise<void> {
|
|
610
|
+
const localDevScript = resolve(dir, "bin/lattices-dev");
|
|
611
|
+
const devScript = existsSync(localDevScript) ? localDevScript : resolve(import.meta.dir, "lattices-dev");
|
|
612
|
+
const { execFileSync } = await import("node:child_process");
|
|
613
|
+
try {
|
|
614
|
+
execFileSync(devScript, [cmd, ...extraFlags], { stdio: "inherit" });
|
|
615
|
+
} catch {
|
|
616
|
+
/* exit code forwarded */
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
562
620
|
async function devCommand(sub?: string, ...flags: string[]): Promise<void> {
|
|
563
621
|
const dir = process.cwd();
|
|
564
622
|
const type = detectProjectType(dir);
|
|
565
623
|
|
|
566
|
-
// Helper to forward to lattices-app.ts
|
|
567
|
-
async function forwardToAppScript(cmd: string, extraFlags: string[] = []): Promise<void> {
|
|
568
|
-
const appScript = resolve(import.meta.dir, "lattices-app.ts");
|
|
569
|
-
const { execFileSync } = await import("node:child_process");
|
|
570
|
-
try {
|
|
571
|
-
execFileSync("bun", [appScript, cmd, ...extraFlags], { stdio: "inherit" });
|
|
572
|
-
} catch { /* exit code forwarded */ }
|
|
573
|
-
}
|
|
574
|
-
|
|
575
624
|
if (!sub) {
|
|
576
625
|
// bare `lattices dev` — run dev server
|
|
577
626
|
if (!type) {
|
|
@@ -580,7 +629,7 @@ async function devCommand(sub?: string, ...flags: string[]): Promise<void> {
|
|
|
580
629
|
}
|
|
581
630
|
console.log(`Detected: ${type} project`);
|
|
582
631
|
if (type === "lattices-app") {
|
|
583
|
-
await
|
|
632
|
+
await forwardToLatticesDevHelper(dir, "restart", flags);
|
|
584
633
|
} else if (type === "node") {
|
|
585
634
|
const cmd = detectDevCommand(dir);
|
|
586
635
|
if (cmd) {
|
|
@@ -604,13 +653,18 @@ async function devCommand(sub?: string, ...flags: string[]): Promise<void> {
|
|
|
604
653
|
return;
|
|
605
654
|
}
|
|
606
655
|
|
|
656
|
+
if (sub === "placement-smoke") {
|
|
657
|
+
await placementSmokeCommand(flags);
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
660
|
+
|
|
607
661
|
if (sub === "build") {
|
|
608
662
|
if (!type) {
|
|
609
663
|
console.log("No recognized project in current directory.");
|
|
610
664
|
return;
|
|
611
665
|
}
|
|
612
666
|
if (type === "lattices-app") {
|
|
613
|
-
await
|
|
667
|
+
await forwardToLatticesDevHelper(dir, "build");
|
|
614
668
|
} else if (type === "swift") {
|
|
615
669
|
console.log("Building: swift build -c release");
|
|
616
670
|
execSync("swift build -c release", { cwd: dir, stdio: "inherit" });
|
|
@@ -638,7 +692,7 @@ async function devCommand(sub?: string, ...flags: string[]): Promise<void> {
|
|
|
638
692
|
|
|
639
693
|
if (sub === "restart") {
|
|
640
694
|
if (type === "lattices-app") {
|
|
641
|
-
await
|
|
695
|
+
await forwardToLatticesDevHelper(dir, "restart", flags);
|
|
642
696
|
} else {
|
|
643
697
|
// For other project types, just rebuild
|
|
644
698
|
await devCommand("build");
|
|
@@ -1095,6 +1149,102 @@ async function placeCommand(query?: string, tilePosition?: string): Promise<void
|
|
|
1095
1149
|
}
|
|
1096
1150
|
}
|
|
1097
1151
|
|
|
1152
|
+
function pause(ms: number): Promise<void> {
|
|
1153
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1154
|
+
}
|
|
1155
|
+
|
|
1156
|
+
function receiptLine(receipt: any): string {
|
|
1157
|
+
const id = receipt?.action?.id || "action";
|
|
1158
|
+
const session = receipt?.session || receipt?.target?.session || "?";
|
|
1159
|
+
const wid = receipt?.wid ?? receipt?.target?.wid ?? "?";
|
|
1160
|
+
const status = receipt?.status || "?";
|
|
1161
|
+
const verified = receipt?.verified === true ? "true" : "false";
|
|
1162
|
+
const resolution = receipt?.targetResolution || "?";
|
|
1163
|
+
return ` ${id} session=${session} wid=${wid} status=${status} verified=${verified} resolution=${resolution}`;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
async function placementSmokeCommand(rawArgs: string[] = []): Promise<void> {
|
|
1167
|
+
const { daemonCall } = await getDaemonClient();
|
|
1168
|
+
const pauseMs = Number(parseFlagValue(rawArgs, "pause") || 1200);
|
|
1169
|
+
const positional = nonFlagArgs(rawArgs);
|
|
1170
|
+
|
|
1171
|
+
let sessions = positional.slice(0, 2);
|
|
1172
|
+
if (sessions.length < 2) {
|
|
1173
|
+
const tmuxSessions = await daemonCall("tmux.sessions") as any[];
|
|
1174
|
+
sessions = tmuxSessions
|
|
1175
|
+
.map(s => s?.name)
|
|
1176
|
+
.filter((name: unknown): name is string => typeof name === "string" && name.startsWith("lattices-place-"))
|
|
1177
|
+
.slice(0, 2);
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
if (sessions.length < 2) {
|
|
1181
|
+
console.log("Need two named sessions. Usage: lattices dev placement-smoke <session-a> <session-b>");
|
|
1182
|
+
console.log("Tip: launch two small lattices fixture projects first, then rerun this command.");
|
|
1183
|
+
return;
|
|
1184
|
+
}
|
|
1185
|
+
|
|
1186
|
+
const [a, b] = sessions;
|
|
1187
|
+
console.log(`Placement smoke: ${a} + ${b}`);
|
|
1188
|
+
|
|
1189
|
+
for (const session of sessions) {
|
|
1190
|
+
const resolved = await daemonCall("window.resolve", {
|
|
1191
|
+
target: { kind: "session", session },
|
|
1192
|
+
placement: "left",
|
|
1193
|
+
}) as any;
|
|
1194
|
+
console.log(` resolve ${session}: wid=${resolved.wid ?? "?"} app=${resolved.app ?? "?"} resolution=${resolved.targetResolution ?? "?"}`);
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
const beats = [
|
|
1198
|
+
{
|
|
1199
|
+
label: "beat 1: halves",
|
|
1200
|
+
actions: [
|
|
1201
|
+
{ id: "a-left-half", type: "window.place", target: { kind: "session", session: a }, args: { placement: "left" } },
|
|
1202
|
+
{ id: "b-right-half", type: "window.place", target: { kind: "session", session: b }, args: { placement: "right" } },
|
|
1203
|
+
],
|
|
1204
|
+
},
|
|
1205
|
+
{
|
|
1206
|
+
label: "beat 2: 4x4 corners",
|
|
1207
|
+
actions: [
|
|
1208
|
+
{ id: "a-top-left-4x4", type: "window.place", target: { kind: "session", session: a }, args: { placement: "grid:4x4:0,0" } },
|
|
1209
|
+
{ id: "b-bottom-right-4x4", type: "window.place", target: { kind: "session", session: b }, args: { placement: "grid:4x4:3,3" } },
|
|
1210
|
+
],
|
|
1211
|
+
},
|
|
1212
|
+
{
|
|
1213
|
+
label: "beat 3: workbench",
|
|
1214
|
+
actions: [
|
|
1215
|
+
{
|
|
1216
|
+
id: "a-workbench-left",
|
|
1217
|
+
type: "window.place",
|
|
1218
|
+
target: { kind: "session", session: a },
|
|
1219
|
+
args: { placement: { kind: "fractions", x: 0.02, y: 0.05, w: 0.62, h: 0.9 } },
|
|
1220
|
+
},
|
|
1221
|
+
{
|
|
1222
|
+
id: "b-console-right",
|
|
1223
|
+
type: "window.place",
|
|
1224
|
+
target: { kind: "session", session: b },
|
|
1225
|
+
args: { placement: { kind: "fractions", x: 0.67, y: 0.12, w: 0.3, h: 0.76 } },
|
|
1226
|
+
},
|
|
1227
|
+
],
|
|
1228
|
+
},
|
|
1229
|
+
];
|
|
1230
|
+
|
|
1231
|
+
for (const beat of beats) {
|
|
1232
|
+
console.log(`\n${beat.label}`);
|
|
1233
|
+
const result = await daemonCall("actions.execute", {
|
|
1234
|
+
source: "placement-smoke",
|
|
1235
|
+
actions: beat.actions,
|
|
1236
|
+
}, 15000) as any;
|
|
1237
|
+
console.log(` batch=${result.status || "?"} request=${result.requestId || "?"}`);
|
|
1238
|
+
for (const receipt of result.receipts || []) {
|
|
1239
|
+
console.log(receiptLine(receipt));
|
|
1240
|
+
}
|
|
1241
|
+
await pause(pauseMs);
|
|
1242
|
+
}
|
|
1243
|
+
|
|
1244
|
+
const focused = await daemonCall("window.focus", { session: a }, 5000) as any;
|
|
1245
|
+
console.log(`\nfocus ${a}: ok=${focused.ok === true} wid=${focused.wid ?? "?"} raised=${focused.raised === true}`);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1098
1248
|
async function sessionsCommand(jsonFlag: boolean): Promise<void> {
|
|
1099
1249
|
try {
|
|
1100
1250
|
const { daemonCall } = await getDaemonClient();
|
|
@@ -1226,6 +1376,787 @@ async function callCommand(method?: string, ...rest: string[]): Promise<void> {
|
|
|
1226
1376
|
}
|
|
1227
1377
|
}
|
|
1228
1378
|
|
|
1379
|
+
interface AppActorAsset {
|
|
1380
|
+
id: string;
|
|
1381
|
+
appName: string;
|
|
1382
|
+
appPath: string;
|
|
1383
|
+
bundleIdentifier?: string;
|
|
1384
|
+
iconPath: string;
|
|
1385
|
+
assetDir: string;
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
function plistValue(plistPath: string, key: string): string | undefined {
|
|
1389
|
+
const value = runQuiet(`/usr/libexec/PlistBuddy -c 'Print :${esc(key)}' '${esc(plistPath)}' 2>/dev/null`);
|
|
1390
|
+
return value?.trim() || undefined;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
function resolveApplication(appQuery: string): string | undefined {
|
|
1394
|
+
const directPath = appQuery.endsWith(".app") ? resolve(appQuery) : undefined;
|
|
1395
|
+
if (directPath && existsSync(directPath)) return directPath.replace(/\/$/, "");
|
|
1396
|
+
|
|
1397
|
+
const script = `POSIX path of (path to application "${appleScriptString(appQuery.replace(/\.app$/i, ""))}")`;
|
|
1398
|
+
const fromLaunchServices = runQuiet(`osascript -e '${esc(script)}' 2>/dev/null`);
|
|
1399
|
+
if (fromLaunchServices) return fromLaunchServices.trim().replace(/\/$/, "");
|
|
1400
|
+
|
|
1401
|
+
const appName = appQuery.endsWith(".app") ? appQuery : `${appQuery}.app`;
|
|
1402
|
+
const fromFind = runQuiet(
|
|
1403
|
+
`find /Applications /System/Applications '${esc(resolve(homedir(), "Applications"))}' -maxdepth 5 -iname '${esc(appName)}' -print -quit 2>/dev/null`
|
|
1404
|
+
);
|
|
1405
|
+
return fromFind?.trim().replace(/\/$/, "") || undefined;
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function resolveApplicationByBundleIdentifier(bundleIdentifier: string): string | undefined {
|
|
1409
|
+
const script = `POSIX path of (path to application id "${appleScriptString(bundleIdentifier)}")`;
|
|
1410
|
+
const fromLaunchServices = runQuiet(`osascript -e '${esc(script)}' 2>/dev/null`);
|
|
1411
|
+
return fromLaunchServices?.trim().replace(/\/$/, "") || undefined;
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1414
|
+
function iconPathForApplication(appPath: string): string | undefined {
|
|
1415
|
+
const resourcesDir = resolve(appPath, "Contents", "Resources");
|
|
1416
|
+
const infoPlist = resolve(appPath, "Contents", "Info.plist");
|
|
1417
|
+
const iconFile = plistValue(infoPlist, "CFBundleIconFile");
|
|
1418
|
+
const candidates: string[] = [];
|
|
1419
|
+
if (iconFile) {
|
|
1420
|
+
candidates.push(resolve(resourcesDir, iconFile));
|
|
1421
|
+
if (!/\.[a-z0-9]+$/i.test(iconFile)) {
|
|
1422
|
+
candidates.push(resolve(resourcesDir, `${iconFile}.icns`));
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
candidates.push(
|
|
1426
|
+
resolve(resourcesDir, "AppIcon.icns"),
|
|
1427
|
+
resolve(resourcesDir, "icon.icns"),
|
|
1428
|
+
resolve(resourcesDir, "electron.icns")
|
|
1429
|
+
);
|
|
1430
|
+
for (const candidate of candidates) {
|
|
1431
|
+
if (existsSync(candidate)) return candidate;
|
|
1432
|
+
}
|
|
1433
|
+
const firstIcns = runQuiet(`find '${esc(resourcesDir)}' -maxdepth 1 -iname '*.icns' -print -quit 2>/dev/null`);
|
|
1434
|
+
return firstIcns?.trim() || undefined;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
function ensureAppActorAsset(appQuery: string): AppActorAsset {
|
|
1438
|
+
const appPath = resolveApplication(appQuery);
|
|
1439
|
+
if (!appPath) {
|
|
1440
|
+
throw new Error(`Could not find application: ${appQuery}`);
|
|
1441
|
+
}
|
|
1442
|
+
|
|
1443
|
+
const appName = basename(appPath, ".app");
|
|
1444
|
+
const iconPath = iconPathForApplication(appPath);
|
|
1445
|
+
if (!iconPath) {
|
|
1446
|
+
throw new Error(`Could not find an icon resource in ${appPath}`);
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
const id = `${slugify(appName)}-icon`;
|
|
1450
|
+
const assetDir = resolve(homedir(), ".codex", "pets", id);
|
|
1451
|
+
const spritesheetPath = resolve(assetDir, "spritesheet.png");
|
|
1452
|
+
mkdirSync(assetDir, { recursive: true });
|
|
1453
|
+
run(`sips -s format png -Z 192 '${esc(iconPath)}' --out '${esc(spritesheetPath)}' >/dev/null`);
|
|
1454
|
+
|
|
1455
|
+
const metadata = {
|
|
1456
|
+
id,
|
|
1457
|
+
displayName: `${appName} Icon`,
|
|
1458
|
+
description: `A one-frame overlay actor made from the ${appName} application icon.`,
|
|
1459
|
+
spritesheetPath: "spritesheet.png",
|
|
1460
|
+
states: {
|
|
1461
|
+
idle: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1462
|
+
thinking: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1463
|
+
working: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1464
|
+
listening: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1465
|
+
waiting: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1466
|
+
ready: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1467
|
+
},
|
|
1468
|
+
};
|
|
1469
|
+
writeFileSync(resolve(assetDir, "pet.json"), `${JSON.stringify(metadata, null, 2)}\n`);
|
|
1470
|
+
|
|
1471
|
+
const bundleIdentifier = plistValue(resolve(appPath, "Contents", "Info.plist"), "CFBundleIdentifier");
|
|
1472
|
+
return { id, appName, appPath, bundleIdentifier, iconPath, assetDir };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
function ensureIconActorAsset(idSeed: string, displayName: string, iconPath: string): string {
|
|
1476
|
+
if (!existsSync(iconPath)) {
|
|
1477
|
+
throw new Error(`HUD icon does not exist: ${iconPath}`);
|
|
1478
|
+
}
|
|
1479
|
+
|
|
1480
|
+
const id = `${slugify(idSeed)}-hud-icon`;
|
|
1481
|
+
const assetDir = resolve(homedir(), ".codex", "pets", id);
|
|
1482
|
+
const spritesheetPath = resolve(assetDir, "spritesheet.png");
|
|
1483
|
+
mkdirSync(assetDir, { recursive: true });
|
|
1484
|
+
run(`sips -s format png -Z 192 '${esc(iconPath)}' --out '${esc(spritesheetPath)}' >/dev/null`);
|
|
1485
|
+
|
|
1486
|
+
const metadata = {
|
|
1487
|
+
id,
|
|
1488
|
+
displayName: `${displayName} HUD Icon`,
|
|
1489
|
+
description: `A one-frame overlay actor icon for the ${displayName} HUD.`,
|
|
1490
|
+
spritesheetPath: "spritesheet.png",
|
|
1491
|
+
states: {
|
|
1492
|
+
idle: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1493
|
+
thinking: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1494
|
+
working: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1495
|
+
listening: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1496
|
+
waiting: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1497
|
+
ready: { row: 0, frames: 1, frameWidth: 192, frameHeight: 192 },
|
|
1498
|
+
},
|
|
1499
|
+
};
|
|
1500
|
+
writeFileSync(resolve(assetDir, "pet.json"), `${JSON.stringify(metadata, null, 2)}\n`);
|
|
1501
|
+
return id;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
function actorUsage(): void {
|
|
1505
|
+
console.log(`Usage:
|
|
1506
|
+
lattices actor app <app-name> [message] [--state=idle] [--x=520 --y=340] [--show-label]
|
|
1507
|
+
lattices actor switcher [app-name ...] [--x=420 --y=220 --gap=270] [--show-label]
|
|
1508
|
+
lattices actor hud <actor-id> <url> [--hud-width=360 --hud-height=240]
|
|
1509
|
+
lattices actor show|hide|toggle|status
|
|
1510
|
+
|
|
1511
|
+
Examples:
|
|
1512
|
+
lattices actor app Codex "Building the release"
|
|
1513
|
+
lattices actor app Talkie "Hover for latest state" --hud-url=http://localhost:5173
|
|
1514
|
+
lattices actor hud switch-talkie http://localhost:5173
|
|
1515
|
+
lattices actor switcher Codex Talkie
|
|
1516
|
+
lattices actor toggle
|
|
1517
|
+
lattices actor switcher "Google Chrome" Codex Talkie --show-label --scale=0.8
|
|
1518
|
+
`);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
async function actorCommand(sub?: string, ...rest: string[]): Promise<void> {
|
|
1522
|
+
if (sub === "app") {
|
|
1523
|
+
await actorAppCommand(rest);
|
|
1524
|
+
return;
|
|
1525
|
+
}
|
|
1526
|
+
if (sub === "switcher") {
|
|
1527
|
+
await actorSwitcherCommand(rest);
|
|
1528
|
+
return;
|
|
1529
|
+
}
|
|
1530
|
+
if (sub === "hud") {
|
|
1531
|
+
await actorHUDCommand(rest);
|
|
1532
|
+
return;
|
|
1533
|
+
}
|
|
1534
|
+
if (sub === "show" || sub === "hide" || sub === "toggle" || sub === "status") {
|
|
1535
|
+
await actorVisibilityCommand(sub, rest);
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
actorUsage();
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
function actorHUDOptions(rest: string[]): Record<string, unknown> {
|
|
1542
|
+
const hudUrl = parseFlagValue(rest, "hud-url") || parseFlagValue(rest, "hudUrl");
|
|
1543
|
+
const hudHTML = parseFlagValue(rest, "hud-html") || parseFlagValue(rest, "hudHTML") || parseFlagValue(rest, "hudHtml");
|
|
1544
|
+
const hudTitle = parseFlagValue(rest, "hud-title") || parseFlagValue(rest, "hudTitle");
|
|
1545
|
+
const hudWidth = parseFlagValue(rest, "hud-width") || parseFlagValue(rest, "hudWidth") || parseFlagValue(rest, "width");
|
|
1546
|
+
const hudHeight = parseFlagValue(rest, "hud-height") || parseFlagValue(rest, "hudHeight") || parseFlagValue(rest, "height");
|
|
1547
|
+
return {
|
|
1548
|
+
...(hudUrl ? { hudUrl } : {}),
|
|
1549
|
+
...(hudHTML ? { hudHTML } : {}),
|
|
1550
|
+
...(hudTitle ? { hudTitle } : {}),
|
|
1551
|
+
...(hudWidth ? { hudWidth: Number(hudWidth) } : {}),
|
|
1552
|
+
...(hudHeight ? { hudHeight: Number(hudHeight) } : {}),
|
|
1553
|
+
};
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
function shouldHideActorLabel(rest: string[]): boolean {
|
|
1557
|
+
if (hasFlag(rest, "show-label") || hasFlag(rest, "showLabel")) return false;
|
|
1558
|
+
return true;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
async function actorHUDCommand(rest: string[]): Promise<void> {
|
|
1562
|
+
const positional = nonFlagArgs(rest);
|
|
1563
|
+
const id = positional[0];
|
|
1564
|
+
if (!id) {
|
|
1565
|
+
actorUsage();
|
|
1566
|
+
return;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
const { daemonCall } = await getDaemonClient();
|
|
1570
|
+
const url = positional[1];
|
|
1571
|
+
const clear = hasFlag(rest, "clear");
|
|
1572
|
+
const result = await daemonCall("overlay.actor.hud", {
|
|
1573
|
+
id,
|
|
1574
|
+
clear,
|
|
1575
|
+
...(url && !clear ? { hudUrl: url } : {}),
|
|
1576
|
+
...actorHUDOptions(rest),
|
|
1577
|
+
}, 15000) as any;
|
|
1578
|
+
|
|
1579
|
+
if (hasFlag(rest, "json")) {
|
|
1580
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1581
|
+
} else if (clear) {
|
|
1582
|
+
console.log(`Cleared HUD for ${id}.`);
|
|
1583
|
+
} else {
|
|
1584
|
+
console.log(`Attached hover HUD to ${id}.`);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1588
|
+
async function actorVisibilityCommand(action: string, rest: string[]): Promise<void> {
|
|
1589
|
+
const { daemonCall } = await getDaemonClient();
|
|
1590
|
+
const result = await daemonCall("overlay.actor.visibility", {
|
|
1591
|
+
action,
|
|
1592
|
+
feedback: !hasFlag(rest, "quiet") && action !== "status",
|
|
1593
|
+
}, 15000) as any;
|
|
1594
|
+
|
|
1595
|
+
if (hasFlag(rest, "json")) {
|
|
1596
|
+
console.log(JSON.stringify(result, null, 2));
|
|
1597
|
+
return;
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1600
|
+
const state = result.visible ? "shown" : "hidden";
|
|
1601
|
+
const count = Number(result.actorCount ?? 0);
|
|
1602
|
+
console.log(`Actor layer ${state} (${count} actor${count === 1 ? "" : "s"}).`);
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
async function actorAppCommand(rest: string[]): Promise<void> {
|
|
1606
|
+
const positional = nonFlagArgs(rest);
|
|
1607
|
+
const appQuery = positional[0];
|
|
1608
|
+
if (!appQuery) {
|
|
1609
|
+
actorUsage();
|
|
1610
|
+
return;
|
|
1611
|
+
}
|
|
1612
|
+
const message = positional.slice(1).join(" ") || `Tap to switch to ${appQuery}.`;
|
|
1613
|
+
const asset = ensureAppActorAsset(appQuery);
|
|
1614
|
+
const { daemonCall } = await getDaemonClient();
|
|
1615
|
+
const id = parseFlagValue(rest, "id") || `app-${slugify(asset.appName)}`;
|
|
1616
|
+
const state = parseFlagValue(rest, "state") || "idle";
|
|
1617
|
+
const ttlMs = Number(parseFlagValue(rest, "ttl") || parseFlagValue(rest, "ttlMs") || 0);
|
|
1618
|
+
const x = Number(parseFlagValue(rest, "x") || 520);
|
|
1619
|
+
const y = Number(parseFlagValue(rest, "y") || 340);
|
|
1620
|
+
const placement = parseFlagValue(rest, "placement") || "point";
|
|
1621
|
+
const style = parseFlagValue(rest, "style") || "playful";
|
|
1622
|
+
const dismissible = hasFlag(rest, "dismissible");
|
|
1623
|
+
const labelHidden = shouldHideActorLabel(rest);
|
|
1624
|
+
const closeOnActivate = hasFlag(rest, "close-on-activate") || hasFlag(rest, "closeOnActivate");
|
|
1625
|
+
const scale = Number(parseFlagValue(rest, "scale") || 1);
|
|
1626
|
+
|
|
1627
|
+
const result = await daemonCall("overlay.actor.publish", {
|
|
1628
|
+
id,
|
|
1629
|
+
renderer: "sprite",
|
|
1630
|
+
asset: asset.id,
|
|
1631
|
+
state,
|
|
1632
|
+
name: parseFlagValue(rest, "name") || asset.appName,
|
|
1633
|
+
message,
|
|
1634
|
+
placement,
|
|
1635
|
+
x,
|
|
1636
|
+
y,
|
|
1637
|
+
style,
|
|
1638
|
+
ttlMs,
|
|
1639
|
+
dismissible,
|
|
1640
|
+
labelHidden,
|
|
1641
|
+
closeOnActivate,
|
|
1642
|
+
scale,
|
|
1643
|
+
...actorHUDOptions(rest),
|
|
1644
|
+
targetApp: asset.appName,
|
|
1645
|
+
targetBundleId: asset.bundleIdentifier,
|
|
1646
|
+
targetAppPath: asset.appPath,
|
|
1647
|
+
}, 15000) as any;
|
|
1648
|
+
|
|
1649
|
+
if (!hasFlag(rest, "no-move")) {
|
|
1650
|
+
await daemonCall("overlay.actor.moveTo", {
|
|
1651
|
+
id,
|
|
1652
|
+
x: x + 40,
|
|
1653
|
+
y: y + 50,
|
|
1654
|
+
durationMs: 700,
|
|
1655
|
+
easing: "spring",
|
|
1656
|
+
}, 15000);
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
if (hasFlag(rest, "json")) {
|
|
1660
|
+
console.log(JSON.stringify({ ...result, asset: asset.id, appPath: asset.appPath }, null, 2));
|
|
1661
|
+
} else {
|
|
1662
|
+
console.log(`Published ${asset.appName} actor (${id}). Click it to switch to ${asset.appName}.`);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
async function actorSwitcherCommand(rest: string[]): Promise<void> {
|
|
1667
|
+
const appNames = nonFlagArgs(rest);
|
|
1668
|
+
const apps = appNames.length ? appNames : ["Codex", "Talkie"];
|
|
1669
|
+
const { daemonCall } = await getDaemonClient();
|
|
1670
|
+
const startX = Number(parseFlagValue(rest, "x") || 420);
|
|
1671
|
+
const y = Number(parseFlagValue(rest, "y") || 220);
|
|
1672
|
+
const gap = Number(parseFlagValue(rest, "gap") || 270);
|
|
1673
|
+
const ttlMs = Number(parseFlagValue(rest, "ttl") || parseFlagValue(rest, "ttlMs") || 0);
|
|
1674
|
+
const style = parseFlagValue(rest, "style") || "info";
|
|
1675
|
+
const dismissible = hasFlag(rest, "dismissible");
|
|
1676
|
+
const labelHidden = shouldHideActorLabel(rest);
|
|
1677
|
+
const closeOnActivate = hasFlag(rest, "close-on-activate") || hasFlag(rest, "closeOnActivate");
|
|
1678
|
+
const scale = Number(parseFlagValue(rest, "scale") || 1);
|
|
1679
|
+
const results: any[] = [];
|
|
1680
|
+
|
|
1681
|
+
for (let i = 0; i < apps.length; i++) {
|
|
1682
|
+
const asset = ensureAppActorAsset(apps[i]);
|
|
1683
|
+
const id = `switch-${slugify(asset.appName)}`;
|
|
1684
|
+
const x = startX + i * gap;
|
|
1685
|
+
const result = await daemonCall("overlay.actor.publish", {
|
|
1686
|
+
id,
|
|
1687
|
+
renderer: "sprite",
|
|
1688
|
+
asset: asset.id,
|
|
1689
|
+
state: "ready",
|
|
1690
|
+
name: asset.appName,
|
|
1691
|
+
message: `Tap to switch to ${asset.appName}.`,
|
|
1692
|
+
placement: "point",
|
|
1693
|
+
x,
|
|
1694
|
+
y,
|
|
1695
|
+
style,
|
|
1696
|
+
ttlMs,
|
|
1697
|
+
dismissible,
|
|
1698
|
+
labelHidden,
|
|
1699
|
+
closeOnActivate,
|
|
1700
|
+
scale,
|
|
1701
|
+
...actorHUDOptions(rest),
|
|
1702
|
+
targetApp: asset.appName,
|
|
1703
|
+
targetBundleId: asset.bundleIdentifier,
|
|
1704
|
+
targetAppPath: asset.appPath,
|
|
1705
|
+
}, 15000) as any;
|
|
1706
|
+
results.push({ ...result, asset: asset.id, appPath: asset.appPath });
|
|
1707
|
+
await daemonCall("overlay.actor.moveTo", {
|
|
1708
|
+
id,
|
|
1709
|
+
x: x + 28,
|
|
1710
|
+
y: y + 36,
|
|
1711
|
+
durationMs: 650,
|
|
1712
|
+
easing: "spring",
|
|
1713
|
+
}, 15000);
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
if (hasFlag(rest, "json")) {
|
|
1717
|
+
console.log(JSON.stringify(results, null, 2));
|
|
1718
|
+
} else {
|
|
1719
|
+
console.log(`Published app switcher for ${apps.join(", ")}.`);
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
type HUDPathField = string | {
|
|
1724
|
+
path?: string;
|
|
1725
|
+
format?: string;
|
|
1726
|
+
schema?: string;
|
|
1727
|
+
presentation?: string;
|
|
1728
|
+
title?: string;
|
|
1729
|
+
description?: string;
|
|
1730
|
+
pollMs?: number;
|
|
1731
|
+
};
|
|
1732
|
+
|
|
1733
|
+
interface HUDManifest {
|
|
1734
|
+
version?: number;
|
|
1735
|
+
manifestVersion?: number;
|
|
1736
|
+
id?: string;
|
|
1737
|
+
name?: string;
|
|
1738
|
+
bundleId?: string;
|
|
1739
|
+
bundleIdentifier?: string;
|
|
1740
|
+
app?: string;
|
|
1741
|
+
appPath?: string;
|
|
1742
|
+
icon?: string;
|
|
1743
|
+
entry?: string;
|
|
1744
|
+
readAccess?: string | string[];
|
|
1745
|
+
state?: HUDPathField;
|
|
1746
|
+
events?: HUDPathField | HUDPathField[];
|
|
1747
|
+
log?: HUDPathField;
|
|
1748
|
+
logs?: HUDPathField[];
|
|
1749
|
+
sources?: HUDPathField[] | Record<string, HUDPathField>;
|
|
1750
|
+
surface?: {
|
|
1751
|
+
width?: number;
|
|
1752
|
+
height?: number;
|
|
1753
|
+
title?: string;
|
|
1754
|
+
transparent?: boolean;
|
|
1755
|
+
};
|
|
1756
|
+
actor?: {
|
|
1757
|
+
id?: string;
|
|
1758
|
+
message?: string;
|
|
1759
|
+
state?: string;
|
|
1760
|
+
x?: number;
|
|
1761
|
+
y?: number;
|
|
1762
|
+
placement?: string;
|
|
1763
|
+
style?: string;
|
|
1764
|
+
scale?: number;
|
|
1765
|
+
labelHidden?: boolean;
|
|
1766
|
+
closeOnActivate?: boolean;
|
|
1767
|
+
click?: string | { type?: string };
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
interface ResolvedHUDManifest {
|
|
1772
|
+
manifestPath: string;
|
|
1773
|
+
rootDir: string;
|
|
1774
|
+
manifest: HUDManifest;
|
|
1775
|
+
id: string;
|
|
1776
|
+
name: string;
|
|
1777
|
+
entry: string;
|
|
1778
|
+
iconPath?: string;
|
|
1779
|
+
appPath?: string;
|
|
1780
|
+
bundleIdentifier?: string;
|
|
1781
|
+
readAccessPath?: string;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
interface HUDRegistryEntry {
|
|
1785
|
+
id: string;
|
|
1786
|
+
name?: string;
|
|
1787
|
+
bundleIdentifier?: string;
|
|
1788
|
+
manifestPath: string;
|
|
1789
|
+
registeredAt: string;
|
|
1790
|
+
lastPublishedAt?: string;
|
|
1791
|
+
}
|
|
1792
|
+
|
|
1793
|
+
interface HUDRegistry {
|
|
1794
|
+
version: 1;
|
|
1795
|
+
entries: HUDRegistryEntry[];
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
function hudUsage(): void {
|
|
1799
|
+
console.log(`Usage:
|
|
1800
|
+
lattices hud register [manifest] [--publish] Register .lattices/hud/manifest.json
|
|
1801
|
+
lattices hud publish [manifest-or-id] Publish one HUD actor now
|
|
1802
|
+
lattices hud sync Publish all registered HUD actors
|
|
1803
|
+
lattices hud list List registered HUDs
|
|
1804
|
+
lattices hud discover [root] [--register] Find HUD manifests under a folder
|
|
1805
|
+
|
|
1806
|
+
Manifest:
|
|
1807
|
+
.lattices/hud/manifest.json
|
|
1808
|
+
|
|
1809
|
+
Examples:
|
|
1810
|
+
lattices hud register .lattices/hud/manifest.json --publish
|
|
1811
|
+
lattices hud publish talkie --x=520 --y=340
|
|
1812
|
+
lattices hud sync
|
|
1813
|
+
`);
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
function hudRegistryPath(): string {
|
|
1817
|
+
return resolve(homedir(), ".lattices", "huds.json");
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
function readHUDRegistry(): HUDRegistry {
|
|
1821
|
+
const path = hudRegistryPath();
|
|
1822
|
+
if (!existsSync(path)) return { version: 1, entries: [] };
|
|
1823
|
+
try {
|
|
1824
|
+
const parsed = JSON.parse(readFileSync(path, "utf8")) as Partial<HUDRegistry>;
|
|
1825
|
+
return {
|
|
1826
|
+
version: 1,
|
|
1827
|
+
entries: Array.isArray(parsed.entries) ? parsed.entries : [],
|
|
1828
|
+
};
|
|
1829
|
+
} catch (e: unknown) {
|
|
1830
|
+
throw new Error(`Invalid HUD registry ${path}: ${(e as Error).message}`);
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
function writeHUDRegistry(registry: HUDRegistry): void {
|
|
1835
|
+
const path = hudRegistryPath();
|
|
1836
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
1837
|
+
writeFileSync(path, `${JSON.stringify(registry, null, 2)}\n`);
|
|
1838
|
+
}
|
|
1839
|
+
|
|
1840
|
+
function isDirectory(path: string): boolean {
|
|
1841
|
+
try {
|
|
1842
|
+
return statSync(path).isDirectory();
|
|
1843
|
+
} catch {
|
|
1844
|
+
return false;
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
|
|
1848
|
+
function isURLLike(value: string): boolean {
|
|
1849
|
+
return /^[a-z][a-z0-9+.-]*:/i.test(value);
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function resolveHUDPath(rootDir: string, value: HUDPathField | undefined, fallback?: string): string | undefined {
|
|
1853
|
+
const raw = typeof value === "string" ? value : value?.path;
|
|
1854
|
+
const path = raw || fallback;
|
|
1855
|
+
if (!path) return undefined;
|
|
1856
|
+
if (isURLLike(path)) return path;
|
|
1857
|
+
if (path.startsWith("~/")) return resolve(homedir(), path.slice(2));
|
|
1858
|
+
return isAbsolute(path) ? path : resolve(rootDir, path);
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function resolveHUDReadAccess(rootDir: string, manifest: HUDManifest, rest: string[] = []): string {
|
|
1862
|
+
const flagValue = parseFlagValue(rest, "read-access") || parseFlagValue(rest, "readAccess");
|
|
1863
|
+
const declared = flagValue
|
|
1864
|
+
?? (Array.isArray(manifest.readAccess) ? manifest.readAccess[0] : manifest.readAccess);
|
|
1865
|
+
if (!declared) return rootDir;
|
|
1866
|
+
if (isURLLike(declared)) return rootDir;
|
|
1867
|
+
if (declared.startsWith("~/")) return resolve(homedir(), declared.slice(2));
|
|
1868
|
+
return isAbsolute(declared) ? declared : resolve(rootDir, declared);
|
|
1869
|
+
}
|
|
1870
|
+
|
|
1871
|
+
function resolveHUDManifestInput(input?: string): string {
|
|
1872
|
+
if (!input) {
|
|
1873
|
+
const defaultPath = resolve(process.cwd(), ".lattices", "hud", "manifest.json");
|
|
1874
|
+
if (existsSync(defaultPath)) return defaultPath;
|
|
1875
|
+
throw new Error("No manifest provided and .lattices/hud/manifest.json was not found.");
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
const candidate = resolve(input);
|
|
1879
|
+
if (existsSync(candidate)) {
|
|
1880
|
+
return isDirectory(candidate) ? resolve(candidate, "manifest.json") : candidate;
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
const registry = readHUDRegistry();
|
|
1884
|
+
const entry = registry.entries.find((item) => item.id === input);
|
|
1885
|
+
if (entry) return entry.manifestPath;
|
|
1886
|
+
|
|
1887
|
+
throw new Error(`HUD manifest or registered id not found: ${input}`);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
function readHUDManifest(input?: string): ResolvedHUDManifest {
|
|
1891
|
+
const manifestPath = resolveHUDManifestInput(input);
|
|
1892
|
+
if (!existsSync(manifestPath)) {
|
|
1893
|
+
throw new Error(`HUD manifest does not exist: ${manifestPath}`);
|
|
1894
|
+
}
|
|
1895
|
+
|
|
1896
|
+
const rootDir = dirname(manifestPath);
|
|
1897
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8")) as HUDManifest;
|
|
1898
|
+
const id = manifest.actor?.id || manifest.id;
|
|
1899
|
+
if (!id) throw new Error(`HUD manifest is missing id: ${manifestPath}`);
|
|
1900
|
+
|
|
1901
|
+
const name = manifest.name || id;
|
|
1902
|
+
const entry = resolveHUDPath(rootDir, manifest.entry, "./index.html");
|
|
1903
|
+
if (!entry) throw new Error(`HUD manifest is missing entry: ${manifestPath}`);
|
|
1904
|
+
if (!isURLLike(entry) && !existsSync(entry)) {
|
|
1905
|
+
throw new Error(`HUD entry does not exist: ${entry}`);
|
|
1906
|
+
}
|
|
1907
|
+
|
|
1908
|
+
const iconPath = resolveHUDPath(rootDir, manifest.icon);
|
|
1909
|
+
const appPath = resolveHUDPath(rootDir, manifest.appPath)
|
|
1910
|
+
?? (manifest.bundleId || manifest.bundleIdentifier
|
|
1911
|
+
? resolveApplicationByBundleIdentifier(manifest.bundleId || manifest.bundleIdentifier || "")
|
|
1912
|
+
: undefined)
|
|
1913
|
+
?? (manifest.app ? resolveApplication(manifest.app) : undefined);
|
|
1914
|
+
const bundleIdentifier = manifest.bundleId
|
|
1915
|
+
?? manifest.bundleIdentifier
|
|
1916
|
+
?? (appPath ? plistValue(resolve(appPath, "Contents", "Info.plist"), "CFBundleIdentifier") : undefined);
|
|
1917
|
+
|
|
1918
|
+
return {
|
|
1919
|
+
manifestPath,
|
|
1920
|
+
rootDir,
|
|
1921
|
+
manifest,
|
|
1922
|
+
id,
|
|
1923
|
+
name,
|
|
1924
|
+
entry,
|
|
1925
|
+
iconPath: iconPath && !isURLLike(iconPath) ? iconPath : undefined,
|
|
1926
|
+
appPath: appPath && !isURLLike(appPath) ? appPath : undefined,
|
|
1927
|
+
bundleIdentifier,
|
|
1928
|
+
readAccessPath: resolveHUDReadAccess(rootDir, manifest),
|
|
1929
|
+
};
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
function numberFlag(rest: string[], name: string, fallback: number): number {
|
|
1933
|
+
const raw = parseFlagValue(rest, name);
|
|
1934
|
+
if (!raw) return fallback;
|
|
1935
|
+
const value = Number(raw);
|
|
1936
|
+
return Number.isFinite(value) ? value : fallback;
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
function numberFlagAny(rest: string[], names: string[], fallback: number): number {
|
|
1940
|
+
for (const name of names) {
|
|
1941
|
+
const raw = parseFlagValue(rest, name);
|
|
1942
|
+
if (!raw) continue;
|
|
1943
|
+
const value = Number(raw);
|
|
1944
|
+
if (Number.isFinite(value)) return value;
|
|
1945
|
+
}
|
|
1946
|
+
return fallback;
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
function hudActorAsset(resolved: ResolvedHUDManifest): string | undefined {
|
|
1950
|
+
if (resolved.iconPath) {
|
|
1951
|
+
return ensureIconActorAsset(resolved.id, resolved.name, resolved.iconPath);
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
const appQuery = resolved.appPath || resolved.manifest.app;
|
|
1955
|
+
if (!appQuery) return undefined;
|
|
1956
|
+
|
|
1957
|
+
try {
|
|
1958
|
+
return ensureAppActorAsset(appQuery).id;
|
|
1959
|
+
} catch {
|
|
1960
|
+
return undefined;
|
|
1961
|
+
}
|
|
1962
|
+
}
|
|
1963
|
+
|
|
1964
|
+
function hudClickType(manifest: HUDManifest): string {
|
|
1965
|
+
const click = manifest.actor?.click;
|
|
1966
|
+
if (!click) return "activateApp";
|
|
1967
|
+
return typeof click === "string" ? click : click.type || "activateApp";
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
function hudPublishPayload(resolved: ResolvedHUDManifest, rest: string[], index = 0): Record<string, unknown> {
|
|
1971
|
+
const manifest = resolved.manifest;
|
|
1972
|
+
const actor = manifest.actor ?? {};
|
|
1973
|
+
const surface = manifest.surface ?? {};
|
|
1974
|
+
const targetEnabled = hudClickType(manifest) !== "none";
|
|
1975
|
+
const asset = hudActorAsset(resolved);
|
|
1976
|
+
const x = numberFlag(rest, "x", actor.x ?? 420 + index * 112);
|
|
1977
|
+
const y = numberFlag(rest, "y", actor.y ?? 220);
|
|
1978
|
+
|
|
1979
|
+
return {
|
|
1980
|
+
id: resolved.id,
|
|
1981
|
+
renderer: "sprite",
|
|
1982
|
+
...(asset ? { asset } : {}),
|
|
1983
|
+
state: parseFlagValue(rest, "state") || actor.state || "ready",
|
|
1984
|
+
name: parseFlagValue(rest, "name") || resolved.name,
|
|
1985
|
+
message: actor.message || `Hover for ${resolved.name} status.`,
|
|
1986
|
+
placement: parseFlagValue(rest, "placement") || actor.placement || "point",
|
|
1987
|
+
x,
|
|
1988
|
+
y,
|
|
1989
|
+
style: parseFlagValue(rest, "style") || actor.style || "info",
|
|
1990
|
+
labelHidden: actor.labelHidden ?? true,
|
|
1991
|
+
closeOnActivate: actor.closeOnActivate ?? false,
|
|
1992
|
+
scale: numberFlag(rest, "scale", actor.scale ?? 1),
|
|
1993
|
+
hudUrl: resolved.entry,
|
|
1994
|
+
hudTitle: surface.title || resolved.name,
|
|
1995
|
+
hudWidth: numberFlagAny(rest, ["hud-width", "hudWidth", "width"], surface.width ?? 380),
|
|
1996
|
+
hudHeight: numberFlagAny(rest, ["hud-height", "hudHeight", "height"], surface.height ?? 260),
|
|
1997
|
+
hudReadAccess: resolveHUDReadAccess(resolved.rootDir, manifest, rest),
|
|
1998
|
+
...(targetEnabled && resolved.bundleIdentifier ? { targetBundleId: resolved.bundleIdentifier } : {}),
|
|
1999
|
+
...(targetEnabled && resolved.appPath ? { targetAppPath: resolved.appPath } : {}),
|
|
2000
|
+
...(targetEnabled && manifest.app ? { targetApp: manifest.app } : {}),
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
function upsertHUDRegistryEntry(resolved: ResolvedHUDManifest, published = false): HUDRegistryEntry {
|
|
2005
|
+
const registry = readHUDRegistry();
|
|
2006
|
+
const now = new Date().toISOString();
|
|
2007
|
+
const existing = registry.entries.find((entry) => entry.id === resolved.id);
|
|
2008
|
+
const next: HUDRegistryEntry = {
|
|
2009
|
+
id: resolved.id,
|
|
2010
|
+
name: resolved.name,
|
|
2011
|
+
bundleIdentifier: resolved.bundleIdentifier,
|
|
2012
|
+
manifestPath: resolved.manifestPath,
|
|
2013
|
+
registeredAt: existing?.registeredAt ?? now,
|
|
2014
|
+
lastPublishedAt: published ? now : existing?.lastPublishedAt,
|
|
2015
|
+
};
|
|
2016
|
+
registry.entries = [
|
|
2017
|
+
next,
|
|
2018
|
+
...registry.entries.filter((entry) => entry.id !== resolved.id),
|
|
2019
|
+
].sort((a, b) => a.id.localeCompare(b.id));
|
|
2020
|
+
writeHUDRegistry(registry);
|
|
2021
|
+
return next;
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
async function publishHUDManifest(resolved: ResolvedHUDManifest, rest: string[], index = 0): Promise<Record<string, unknown>> {
|
|
2025
|
+
const { daemonCall } = await getDaemonClient();
|
|
2026
|
+
const payload = hudPublishPayload(resolved, rest, index);
|
|
2027
|
+
const result = await daemonCall("overlay.actor.publish", payload, 15000) as Record<string, unknown>;
|
|
2028
|
+
if (!hasFlag(rest, "no-move")) {
|
|
2029
|
+
await daemonCall("overlay.actor.moveTo", {
|
|
2030
|
+
id: resolved.id,
|
|
2031
|
+
x: Number(payload.x) + 24,
|
|
2032
|
+
y: Number(payload.y) + 30,
|
|
2033
|
+
durationMs: 600,
|
|
2034
|
+
easing: "spring",
|
|
2035
|
+
}, 15000);
|
|
2036
|
+
}
|
|
2037
|
+
upsertHUDRegistryEntry(resolved, true);
|
|
2038
|
+
return result;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2041
|
+
async function hudRegisterCommand(rest: string[]): Promise<void> {
|
|
2042
|
+
const manifestArg = nonFlagArgs(rest)[0] || parseFlagValue(rest, "manifest");
|
|
2043
|
+
const resolved = readHUDManifest(manifestArg);
|
|
2044
|
+
const entry = upsertHUDRegistryEntry(resolved, false);
|
|
2045
|
+
|
|
2046
|
+
if (hasFlag(rest, "publish")) {
|
|
2047
|
+
await publishHUDManifest(resolved, rest);
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
const published = hasFlag(rest, "publish");
|
|
2051
|
+
if (hasFlag(rest, "json")) {
|
|
2052
|
+
console.log(JSON.stringify(entry, null, 2));
|
|
2053
|
+
} else {
|
|
2054
|
+
console.log(`${published ? "Registered and published" : "Registered"} HUD ${resolved.id} -> ${resolved.manifestPath}`);
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
async function hudPublishCommand(rest: string[]): Promise<void> {
|
|
2059
|
+
const manifestArg = nonFlagArgs(rest)[0] || parseFlagValue(rest, "manifest");
|
|
2060
|
+
const resolved = readHUDManifest(manifestArg);
|
|
2061
|
+
const result = await publishHUDManifest(resolved, rest);
|
|
2062
|
+
|
|
2063
|
+
if (hasFlag(rest, "json")) {
|
|
2064
|
+
console.log(JSON.stringify({ ...result, manifestPath: resolved.manifestPath }, null, 2));
|
|
2065
|
+
} else {
|
|
2066
|
+
console.log(`Published HUD actor ${resolved.id}. Hover it for ${resolved.name}.`);
|
|
2067
|
+
}
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
async function hudSyncCommand(rest: string[]): Promise<void> {
|
|
2071
|
+
const registry = readHUDRegistry();
|
|
2072
|
+
const results: Record<string, unknown>[] = [];
|
|
2073
|
+
for (let i = 0; i < registry.entries.length; i++) {
|
|
2074
|
+
const resolved = readHUDManifest(registry.entries[i].id);
|
|
2075
|
+
results.push(await publishHUDManifest(resolved, rest, i));
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
if (hasFlag(rest, "json")) {
|
|
2079
|
+
console.log(JSON.stringify(results, null, 2));
|
|
2080
|
+
} else {
|
|
2081
|
+
console.log(`Published ${results.length} registered HUD actor${results.length === 1 ? "" : "s"}.`);
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
function hudListCommand(rest: string[]): void {
|
|
2086
|
+
const registry = readHUDRegistry();
|
|
2087
|
+
if (hasFlag(rest, "json")) {
|
|
2088
|
+
console.log(JSON.stringify(registry, null, 2));
|
|
2089
|
+
return;
|
|
2090
|
+
}
|
|
2091
|
+
if (!registry.entries.length) {
|
|
2092
|
+
console.log("No registered HUDs. Run lattices hud register .lattices/hud/manifest.json");
|
|
2093
|
+
return;
|
|
2094
|
+
}
|
|
2095
|
+
console.log("Registered HUDs:\n");
|
|
2096
|
+
for (const entry of registry.entries) {
|
|
2097
|
+
console.log(` ${entry.id}${entry.name ? ` (${entry.name})` : ""}`);
|
|
2098
|
+
console.log(` manifest: ${entry.manifestPath}`);
|
|
2099
|
+
if (entry.bundleIdentifier) console.log(` bundle: ${entry.bundleIdentifier}`);
|
|
2100
|
+
if (entry.lastPublishedAt) console.log(` shown: ${entry.lastPublishedAt}`);
|
|
2101
|
+
console.log();
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
|
|
2105
|
+
function hudDiscoverCommand(rest: string[]): void {
|
|
2106
|
+
const root = resolve(nonFlagArgs(rest)[0] || parseFlagValue(rest, "root") || process.cwd());
|
|
2107
|
+
const maxDepth = Number(parseFlagValue(rest, "max-depth") || parseFlagValue(rest, "maxDepth") || 6);
|
|
2108
|
+
const out = runQuiet(`find '${esc(root)}' -maxdepth ${maxDepth} -path '*/.lattices/hud/manifest.json' -print 2>/dev/null`);
|
|
2109
|
+
const manifests = out ? out.split("\n").filter(Boolean) : [];
|
|
2110
|
+
|
|
2111
|
+
if (hasFlag(rest, "register")) {
|
|
2112
|
+
for (const manifestPath of manifests) {
|
|
2113
|
+
upsertHUDRegistryEntry(readHUDManifest(manifestPath), false);
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
if (hasFlag(rest, "json")) {
|
|
2118
|
+
console.log(JSON.stringify(manifests, null, 2));
|
|
2119
|
+
return;
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
if (!manifests.length) {
|
|
2123
|
+
console.log(`No HUD manifests found under ${root}`);
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
for (const manifestPath of manifests) console.log(manifestPath);
|
|
2127
|
+
if (hasFlag(rest, "register")) {
|
|
2128
|
+
console.log(`\nRegistered ${manifests.length} HUD manifest${manifests.length === 1 ? "" : "s"}.`);
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
async function hudCommand(sub?: string, ...rest: string[]): Promise<void> {
|
|
2133
|
+
try {
|
|
2134
|
+
switch (sub) {
|
|
2135
|
+
case "register":
|
|
2136
|
+
await hudRegisterCommand(rest);
|
|
2137
|
+
return;
|
|
2138
|
+
case "publish":
|
|
2139
|
+
case "show":
|
|
2140
|
+
await hudPublishCommand(rest);
|
|
2141
|
+
return;
|
|
2142
|
+
case "sync":
|
|
2143
|
+
await hudSyncCommand(rest);
|
|
2144
|
+
return;
|
|
2145
|
+
case "list":
|
|
2146
|
+
case "ls":
|
|
2147
|
+
hudListCommand(rest);
|
|
2148
|
+
return;
|
|
2149
|
+
case "discover":
|
|
2150
|
+
hudDiscoverCommand(rest);
|
|
2151
|
+
return;
|
|
2152
|
+
default:
|
|
2153
|
+
hudUsage();
|
|
2154
|
+
}
|
|
2155
|
+
} catch (e: unknown) {
|
|
2156
|
+
console.log(`Error: ${(e as Error).message}`);
|
|
2157
|
+
}
|
|
2158
|
+
}
|
|
2159
|
+
|
|
1229
2160
|
async function layerCommand(sub?: string, ...rest: string[]): Promise<void> {
|
|
1230
2161
|
try {
|
|
1231
2162
|
const { daemonCall } = await getDaemonClient();
|
|
@@ -1749,6 +2680,12 @@ Usage:
|
|
|
1749
2680
|
lattices voice status Voice provider status
|
|
1750
2681
|
lattices voice simulate <t> Parse and execute a voice command
|
|
1751
2682
|
lattices voice intents List all available intents
|
|
2683
|
+
lattices actor app <app> [message] Show a clickable app-icon actor
|
|
2684
|
+
lattices actor switcher [apps...] Show a clickable app switcher row
|
|
2685
|
+
lattices actor hud <id> <url> Attach a hover web HUD to an actor
|
|
2686
|
+
lattices actor toggle Hide/show the sticky actor layer
|
|
2687
|
+
lattices hud register [manifest] Register a .lattices/hud/manifest.json
|
|
2688
|
+
lattices hud publish [id|manifest] Publish a registered/static HUD actor
|
|
1752
2689
|
lattices assistant plan <t> Preview the TS assistant planner
|
|
1753
2690
|
lattices call <method> [p] Raw daemon API call (params as JSON)
|
|
1754
2691
|
lattices scan Show text from all visible windows
|
|
@@ -1760,11 +2697,12 @@ Usage:
|
|
|
1760
2697
|
lattices dev Run dev server (auto-detected)
|
|
1761
2698
|
lattices dev build Build the project (swift/node/rust/go/make)
|
|
1762
2699
|
lattices dev restart Build + restart (swift app) or just build
|
|
2700
|
+
lattices dev placement-smoke [a] [b] Move two named sessions through verified placements
|
|
1763
2701
|
lattices dev type Print detected project type
|
|
1764
2702
|
lattices mouse Find mouse — sonar pulse at cursor position
|
|
1765
2703
|
lattices mouse summon Summon mouse to screen center
|
|
1766
2704
|
lattices daemon status Show daemon status
|
|
1767
|
-
lattices
|
|
2705
|
+
lattices logs [limit] Show activity log entries (aliases: log, activity, diag)
|
|
1768
2706
|
lattices app Launch the menu bar companion app
|
|
1769
2707
|
lattices app update Download the latest menu bar app and relaunch
|
|
1770
2708
|
lattices app build Rebuild the menu bar app
|
|
@@ -2288,6 +3226,14 @@ switch (command) {
|
|
|
2288
3226
|
case "voice":
|
|
2289
3227
|
await voiceCommand(args[1], ...args.slice(2));
|
|
2290
3228
|
break;
|
|
3229
|
+
case "actor":
|
|
3230
|
+
case "actors":
|
|
3231
|
+
await actorCommand(args[1], ...args.slice(2));
|
|
3232
|
+
break;
|
|
3233
|
+
case "hud":
|
|
3234
|
+
case "huds":
|
|
3235
|
+
await hudCommand(args[1], ...args.slice(2));
|
|
3236
|
+
break;
|
|
2291
3237
|
case "assistant":
|
|
2292
3238
|
await assistantCommand(args[1], ...args.slice(2));
|
|
2293
3239
|
break;
|
|
@@ -2300,6 +3246,9 @@ switch (command) {
|
|
|
2300
3246
|
break;
|
|
2301
3247
|
case "diag":
|
|
2302
3248
|
case "diagnostics":
|
|
3249
|
+
case "log":
|
|
3250
|
+
case "logs":
|
|
3251
|
+
case "activity":
|
|
2303
3252
|
await diagCommand(args[1]);
|
|
2304
3253
|
break;
|
|
2305
3254
|
case "scan":
|
|
@@ -2320,8 +3269,20 @@ switch (command) {
|
|
|
2320
3269
|
await devCommand(args[1], ...args.slice(2));
|
|
2321
3270
|
break;
|
|
2322
3271
|
case "app": {
|
|
2323
|
-
// Forward to lattices-app script
|
|
2324
3272
|
const { execFileSync } = await import("node:child_process");
|
|
3273
|
+
const dir = process.cwd();
|
|
3274
|
+
const first = args[1];
|
|
3275
|
+
const appSubcommand = first && !first.startsWith("-") ? first : "launch";
|
|
3276
|
+
const appFlags = first && !first.startsWith("-") ? args.slice(2) : args.slice(1);
|
|
3277
|
+
const devAppCommands = new Set(["launch", "start", "build", "restart", "quit", "stop"]);
|
|
3278
|
+
|
|
3279
|
+
if (detectProjectType(dir) === "lattices-app" && devAppCommands.has(appSubcommand)) {
|
|
3280
|
+
console.log("Using local dev app bundle so macOS permissions stay attached across rebuilds.");
|
|
3281
|
+
await forwardToLatticesDevHelper(dir, appSubcommand, appFlags);
|
|
3282
|
+
break;
|
|
3283
|
+
}
|
|
3284
|
+
|
|
3285
|
+
// Forward release/package app commands to lattices-app script.
|
|
2325
3286
|
const appScript = resolve(import.meta.dir, "lattices-app.ts");
|
|
2326
3287
|
try {
|
|
2327
3288
|
execFileSync("bun", [appScript, ...args.slice(1)], { stdio: "inherit" });
|