@lattices/cli 0.4.10 → 0.4.12
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/LICENSE +21 -0
- package/README.md +13 -13
- package/{app → apps/mac}/Lattices.app/Contents/Info.plist +10 -2
- package/{app → apps/mac}/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/{app → apps/mac}/Package.swift +2 -1
- package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
- package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
- package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
- package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
- package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +27 -0
- package/apps/mac/Sources/AppShell/AppDelegate.swift +189 -0
- package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +25 -0
- package/{app → apps/mac}/Sources/AppShell/AppShellView.swift +18 -3
- package/{app → apps/mac}/Sources/AppShell/AppUpdater.swift +4 -3
- package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +87 -0
- package/{app → apps/mac}/Sources/AppShell/LatticesRuntime.swift +43 -0
- package/{app → apps/mac}/Sources/AppShell/MainView.swift +116 -63
- package/apps/mac/Sources/AppShell/MenuBarController.swift +177 -0
- package/{app → apps/mac}/Sources/AppShell/OnboardingView.swift +72 -60
- package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +366 -0
- package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +70 -0
- package/{app → apps/mac}/Sources/AppShell/Preferences.swift +37 -2
- package/{app → apps/mac}/Sources/AppShell/SettingsView.swift +815 -156
- package/{app → apps/mac}/Sources/AppShell/SettingsWindow.swift +10 -0
- package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +13 -0
- package/{app → apps/mac}/Sources/Core/Actions/HotkeyStore.swift +6 -1
- package/{app → apps/mac}/Sources/Core/Actions/IntentEngine.swift +2 -0
- package/{app → apps/mac}/Sources/Core/Daemon/DaemonServer.swift +5 -0
- package/{app → apps/mac}/Sources/Core/Daemon/LatticesApi.swift +365 -0
- package/{app → apps/mac}/Sources/Core/Desktop/OcrModel.swift +17 -13
- package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +33 -0
- package/{app → apps/mac}/Sources/Core/Desktop/WindowDragSnapController.swift +18 -217
- package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewStore.swift +4 -5
- package/{app → apps/mac}/Sources/Core/Desktop/WindowTiler.swift +19 -13
- package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +124 -0
- package/apps/mac/Sources/Core/Input/EventTapThread.swift +54 -0
- package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +20 -0
- package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +335 -0
- package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +141 -0
- package/{app → apps/mac}/Sources/Core/Input/MouseGestureConfig.swift +155 -20
- package/apps/mac/Sources/Core/Input/MouseGestureController.swift +2271 -0
- package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +170 -0
- package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +39 -0
- package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +624 -0
- package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +56 -0
- package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +8 -8
- package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +1264 -0
- package/{app → apps/mac}/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +11 -23
- package/{app → apps/mac}/Sources/Core/Pi/PiChatDock.swift +90 -43
- package/{app → apps/mac}/Sources/Core/Pi/PiChatSession.swift +676 -43
- package/{app → apps/mac}/Sources/Core/Pi/PiProviderSetupCallout.swift +5 -5
- package/{app → apps/mac}/Sources/Core/Pi/PiWorkspaceView.swift +93 -44
- package/apps/mac/Sources/Core/System/Capability.swift +79 -0
- package/{app → apps/mac}/Sources/Core/System/PermissionChecker.swift +43 -8
- package/{app → apps/mac}/Sources/Core/Voice/AudioProvider.swift +225 -56
- package/bin/handsoff-infer.ts +14 -5
- package/bin/handsoff-worker.ts +11 -7
- package/bin/infer.ts +406 -0
- package/bin/lattices-app.ts +57 -7
- package/bin/lattices-dev +40 -1
- package/bin/lattices.ts +1 -1
- package/docs/agent-execution-plan.md +9 -9
- package/docs/api.md +119 -0
- package/docs/app.md +1 -0
- package/docs/companion-deck.md +1 -1
- package/docs/gesture-customization-proposal.md +520 -0
- package/docs/mouse-gestures.md +79 -0
- package/docs/overview.md +2 -2
- package/docs/presentation-execution-review.md +9 -9
- package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
- package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
- package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
- package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
- package/docs/reference/dewey.config.ts +74 -0
- package/docs/reference/install-agent.md +79 -0
- package/docs/repo-structure.md +100 -0
- package/docs/voice-error-model.md +7 -7
- package/docs/voice.md +18 -0
- package/package.json +23 -13
- package/swift/Package.swift +20 -0
- package/swift/Sources/DeckKit/DeckAction.swift +51 -0
- package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +152 -0
- package/swift/Sources/DeckKit/DeckCockpit.swift +82 -0
- package/swift/Sources/DeckKit/DeckHost.swift +7 -0
- package/swift/Sources/DeckKit/DeckManifest.swift +145 -0
- package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +533 -0
- package/swift/Sources/DeckKit/DeckTrackpad.swift +63 -0
- package/swift/Sources/DeckKit/DeckValue.swift +93 -0
- package/swift/Sources/DeckKit/DeckVoiceError.swift +88 -0
- package/swift/Tests/DeckKitTests/DeckKitTests.swift +286 -0
- package/app/Sources/AppShell/AppDelegate.swift +0 -408
- package/app/Sources/Core/Input/KeyboardRemapController.swift +0 -184
- package/app/Sources/Core/Input/KeyboardRemapStore.swift +0 -84
- package/app/Sources/Core/Input/MouseGestureController.swift +0 -1203
- package/app/Sources/Core/Input/MouseShortcutStore.swift +0 -107
- /package/{app → apps/mac}/Info.plist +0 -0
- /package/{app → apps/mac}/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
- /package/{app → apps/mac}/Lattices.app/Contents/Resources/tap.wav +0 -0
- /package/{app → apps/mac}/Lattices.app/Contents/_CodeSignature/CodeResources +0 -0
- /package/{app → apps/mac}/Lattices.entitlements +0 -0
- /package/{app → apps/mac}/Resources/tap.wav +0 -0
- /package/{app → apps/mac}/Sources/AppShell/App.swift +0 -0
- /package/{app → apps/mac}/Sources/AppShell/CliActionLauncher.swift +0 -0
- /package/{app → apps/mac}/Sources/AppShell/HomeDashboardView.swift +0 -0
- /package/{app → apps/mac}/Sources/AppShell/KeyRecorderView.swift +0 -0
- /package/{app → apps/mac}/Sources/AppShell/MainWindow.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/HotkeyManager.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/IntentSchema.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/FocusIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/HelpIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/KillIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/ScanIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/SearchIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/Intents/TileIntent.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/PaletteCommand.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Actions/VoiceIntentResolver.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/CompanionActivityLog.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/CompanionKeyboardController.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Companion/LatticesDeckHost.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Daemon/DaemonProtocol.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/AppTypeClassifier.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModel.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModelTypes.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/InventoryManager.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/InventoryPath.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/MouseFinder.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/OcrStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/PlacementSpec.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/SessionWindowLocator.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/TilePickerView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewCard.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Desktop/WindowSelectionStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Input/KeyboardRemapConfig.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Input/MouseInputDeviceStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Input/MouseInputEventViewer.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/AppWindowShell.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDController.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDState.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/OverlayPanelShell.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Pi/PiAuthPromptCard.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Pi/PiInstallCallout.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/DiagnosticLog.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/EventBus.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/ProcessModel.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/ProcessQuery.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/System/SystemTelemetryMonitor.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/AdvisorLearningStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/AgentSession.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/HandsOffSession.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/VoiceChatView.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Voice/VoxClient.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Project.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/ProjectScanner.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/SessionLayerStore.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/SessionManager.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/Terminal.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -0
- /package/{app → apps/mac}/Sources/Core/Workspace/WorkspaceManager.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/ActionRow.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/OrphanRow.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/ProjectRow.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/TabGroupRow.swift +0 -0
- /package/{app → apps/mac}/Sources/UI/Theme.swift +0 -0
- /package/{app → apps/mac}/Tests/StageDragTests.swift +0 -0
- /package/{app → apps/mac}/Tests/StageJoinTests.swift +0 -0
- /package/{app → apps/mac}/Tests/StageManagerTests.swift +0 -0
- /package/{app → apps/mac}/Tests/StageTileTests.swift +0 -0
package/bin/infer.ts
ADDED
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lattices inference wrapper — thin layer over Vercel AI SDK.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Multi-provider: groq, openai, anthropic, google, xai
|
|
6
|
+
* - Credential loading: env vars → .env.local/.env → ~/.lattices/inference.json → ~/.config/speakeasy/settings.json
|
|
7
|
+
* - Instrumented: every call logged with timing, model, token usage
|
|
8
|
+
* - Simple API: `await infer("do something", { provider: "groq" })`
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { generateText, type ModelMessage } from "ai";
|
|
12
|
+
import { createOpenAI } from "@ai-sdk/openai";
|
|
13
|
+
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
14
|
+
import { createGoogleGenerativeAI } from "@ai-sdk/google";
|
|
15
|
+
import { createXai } from "@ai-sdk/xai";
|
|
16
|
+
import { readFileSync, existsSync } from "fs";
|
|
17
|
+
import { homedir } from "os";
|
|
18
|
+
import { join } from "path";
|
|
19
|
+
|
|
20
|
+
// ── Types ──────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export type ProviderName = "groq" | "openai" | "anthropic" | "google" | "xai" | "minimax";
|
|
23
|
+
|
|
24
|
+
export interface InferOptions {
|
|
25
|
+
provider?: ProviderName;
|
|
26
|
+
model?: string;
|
|
27
|
+
system?: string;
|
|
28
|
+
messages?: ModelMessage[];
|
|
29
|
+
temperature?: number;
|
|
30
|
+
maxTokens?: number;
|
|
31
|
+
/** Tag for logging — e.g. "hands-off", "voice-fallback" */
|
|
32
|
+
tag?: string;
|
|
33
|
+
/** Abort signal for cancellation/timeout */
|
|
34
|
+
abortSignal?: AbortSignal;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface InferResult {
|
|
38
|
+
text: string;
|
|
39
|
+
provider: ProviderName;
|
|
40
|
+
model: string;
|
|
41
|
+
durationMs: number;
|
|
42
|
+
usage?: {
|
|
43
|
+
promptTokens?: number;
|
|
44
|
+
completionTokens?: number;
|
|
45
|
+
totalTokens?: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Default models per provider ────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
const PROVIDER_NAMES: ProviderName[] = ["groq", "openai", "anthropic", "google", "xai", "minimax"];
|
|
52
|
+
const VOICE_PROVIDER_PRIORITY: ProviderName[] = ["groq", "xai", "openai", "google", "anthropic", "minimax"];
|
|
53
|
+
|
|
54
|
+
const DEFAULT_MODELS: Record<ProviderName, string> = {
|
|
55
|
+
groq: "llama-3.3-70b-versatile",
|
|
56
|
+
openai: "gpt-4o-mini",
|
|
57
|
+
anthropic: "claude-sonnet-4-6",
|
|
58
|
+
google: "gemini-2.0-flash",
|
|
59
|
+
xai: "grok-4-1-fast-non-reasoning",
|
|
60
|
+
minimax: "MiniMax-M2.5-highspeed",
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const VOICE_DEFAULT_MODELS: Record<ProviderName, string> = {
|
|
64
|
+
...DEFAULT_MODELS,
|
|
65
|
+
groq: "llama-3.1-8b-instant",
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ── Credential loading ─────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
interface CredentialStore {
|
|
71
|
+
groq?: string;
|
|
72
|
+
openai?: string;
|
|
73
|
+
anthropic?: string;
|
|
74
|
+
google?: string;
|
|
75
|
+
xai?: string;
|
|
76
|
+
minimax?: string;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let _cachedCreds: CredentialStore | null = null;
|
|
80
|
+
let _cachedLocalEnv: Record<string, string> | null = null;
|
|
81
|
+
|
|
82
|
+
function parseDotEnv(content: string): Record<string, string> {
|
|
83
|
+
const env: Record<string, string> = {};
|
|
84
|
+
|
|
85
|
+
for (const rawLine of content.split(/\r?\n/)) {
|
|
86
|
+
const line = rawLine.trim();
|
|
87
|
+
if (!line || line.startsWith("#")) continue;
|
|
88
|
+
|
|
89
|
+
const match = line.match(/^(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
90
|
+
if (!match) continue;
|
|
91
|
+
|
|
92
|
+
const [, key, rawValue] = match;
|
|
93
|
+
let value = rawValue.trim();
|
|
94
|
+
const quote = value[0];
|
|
95
|
+
if ((quote === `"` || quote === `'`) && value.endsWith(quote)) {
|
|
96
|
+
value = value.slice(1, -1);
|
|
97
|
+
} else {
|
|
98
|
+
value = value.replace(/\s+#.*$/, "").trim();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
env[key] = value;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return env;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function loadLocalEnv(): Record<string, string> {
|
|
108
|
+
if (_cachedLocalEnv) return _cachedLocalEnv;
|
|
109
|
+
|
|
110
|
+
const repoRoot = join(import.meta.dir, "..");
|
|
111
|
+
const candidates = [
|
|
112
|
+
join(repoRoot, ".env"),
|
|
113
|
+
join(repoRoot, ".env.local"),
|
|
114
|
+
join(process.cwd(), ".env"),
|
|
115
|
+
join(process.cwd(), ".env.local"),
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
const env: Record<string, string> = {};
|
|
119
|
+
for (const file of Array.from(new Set(candidates))) {
|
|
120
|
+
if (!existsSync(file)) continue;
|
|
121
|
+
try {
|
|
122
|
+
Object.assign(env, parseDotEnv(readFileSync(file, "utf-8")));
|
|
123
|
+
} catch {}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
_cachedLocalEnv = env;
|
|
127
|
+
return env;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function getInferenceEnv(name: string): string | undefined {
|
|
131
|
+
return process.env[name] || loadLocalEnv()[name];
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function firstInferenceEnv(names: string[]): string | undefined {
|
|
135
|
+
for (const name of names) {
|
|
136
|
+
const value = getInferenceEnv(name);
|
|
137
|
+
if (value) return value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function normalizeProvider(value: string | undefined): ProviderName | undefined {
|
|
142
|
+
const provider = value?.trim().toLowerCase();
|
|
143
|
+
return PROVIDER_NAMES.includes(provider as ProviderName) ? (provider as ProviderName) : undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function assignGrokAlias(creds: CredentialStore) {
|
|
147
|
+
const key = getInferenceEnv("GROK_API_KEY");
|
|
148
|
+
if (!key) return;
|
|
149
|
+
|
|
150
|
+
// People often say/type "Grok" when they mean Groq. Use the key shape to
|
|
151
|
+
// route the alias without making xAI and Groq credentials interchangeable.
|
|
152
|
+
if (!creds.groq && key.startsWith("gsk_")) creds.groq = key;
|
|
153
|
+
if (!creds.xai && key.startsWith("xai-")) creds.xai = key;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function loadCredentials(): CredentialStore {
|
|
157
|
+
if (_cachedCreds) return _cachedCreds;
|
|
158
|
+
|
|
159
|
+
const creds: CredentialStore = {};
|
|
160
|
+
|
|
161
|
+
// Layer 1: env vars (highest priority)
|
|
162
|
+
const groqKey = getInferenceEnv("GROQ_API_KEY");
|
|
163
|
+
const openaiKey = getInferenceEnv("OPENAI_API_KEY");
|
|
164
|
+
const anthropicKey = getInferenceEnv("ANTHROPIC_API_KEY");
|
|
165
|
+
const googleKey = getInferenceEnv("GOOGLE_GENERATIVE_AI_API_KEY");
|
|
166
|
+
const xaiKey = getInferenceEnv("XAI_API_KEY");
|
|
167
|
+
const minimaxKey = getInferenceEnv("MINIMAX_API_KEY");
|
|
168
|
+
if (groqKey) creds.groq = groqKey;
|
|
169
|
+
if (openaiKey) creds.openai = openaiKey;
|
|
170
|
+
if (anthropicKey) creds.anthropic = anthropicKey;
|
|
171
|
+
if (googleKey) creds.google = googleKey;
|
|
172
|
+
if (xaiKey) creds.xai = xaiKey;
|
|
173
|
+
if (minimaxKey) creds.minimax = minimaxKey;
|
|
174
|
+
assignGrokAlias(creds);
|
|
175
|
+
|
|
176
|
+
// Layer 2: ~/.lattices/inference.json
|
|
177
|
+
const latticesConfig = join(homedir(), ".lattices", "inference.json");
|
|
178
|
+
if (existsSync(latticesConfig)) {
|
|
179
|
+
try {
|
|
180
|
+
const cfg = JSON.parse(readFileSync(latticesConfig, "utf-8"));
|
|
181
|
+
if (cfg.keys) {
|
|
182
|
+
if (!creds.groq && cfg.keys.groq) creds.groq = cfg.keys.groq;
|
|
183
|
+
if (!creds.openai && cfg.keys.openai) creds.openai = cfg.keys.openai;
|
|
184
|
+
if (!creds.anthropic && cfg.keys.anthropic) creds.anthropic = cfg.keys.anthropic;
|
|
185
|
+
if (!creds.google && cfg.keys.google) creds.google = cfg.keys.google;
|
|
186
|
+
if (!creds.xai && cfg.keys.xai) creds.xai = cfg.keys.xai;
|
|
187
|
+
if (!creds.minimax && cfg.keys.minimax) creds.minimax = cfg.keys.minimax;
|
|
188
|
+
}
|
|
189
|
+
} catch {}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Layer 3: ~/.config/speakeasy/settings.json (fallback)
|
|
193
|
+
const speakeasyConfig = join(homedir(), ".config", "speakeasy", "settings.json");
|
|
194
|
+
if (existsSync(speakeasyConfig)) {
|
|
195
|
+
try {
|
|
196
|
+
const cfg = JSON.parse(readFileSync(speakeasyConfig, "utf-8"));
|
|
197
|
+
const p = cfg.providers || {};
|
|
198
|
+
if (!creds.groq && p.groq?.apiKey) creds.groq = p.groq.apiKey;
|
|
199
|
+
if (!creds.openai && p.openai?.apiKey) creds.openai = p.openai.apiKey;
|
|
200
|
+
if (!creds.anthropic && p.anthropic?.apiKey) creds.anthropic = p.anthropic.apiKey;
|
|
201
|
+
if (!creds.google && p.gemini?.apiKey) creds.google = p.gemini.apiKey;
|
|
202
|
+
if (!creds.xai && p.xai?.apiKey) creds.xai = p.xai.apiKey;
|
|
203
|
+
if (!creds.minimax && p.minimax?.apiKey) creds.minimax = p.minimax.apiKey;
|
|
204
|
+
} catch {}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
_cachedCreds = creds;
|
|
208
|
+
return creds;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Clear cached credentials (call if config changes at runtime) */
|
|
212
|
+
export function clearCredentialCache() {
|
|
213
|
+
_cachedCreds = null;
|
|
214
|
+
_cachedLocalEnv = null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** List which providers have credentials available */
|
|
218
|
+
export function availableProviders(): ProviderName[] {
|
|
219
|
+
const creds = loadCredentials();
|
|
220
|
+
return (Object.keys(creds) as ProviderName[]).filter((k) => !!creds[k]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Voice/hands-off defaults favor the lowest-latency configured provider. */
|
|
224
|
+
export function resolveVoiceInferenceOptions(): { provider: ProviderName; model: string } {
|
|
225
|
+
const configuredProvider = normalizeProvider(firstInferenceEnv([
|
|
226
|
+
"LATTICES_VOICE_PROVIDER",
|
|
227
|
+
"LATTICES_HANDSOFF_PROVIDER",
|
|
228
|
+
"LATTICES_INFER_PROVIDER",
|
|
229
|
+
]));
|
|
230
|
+
|
|
231
|
+
const creds = loadCredentials();
|
|
232
|
+
const provider = configuredProvider
|
|
233
|
+
?? VOICE_PROVIDER_PRIORITY.find((name) => !!creds[name])
|
|
234
|
+
?? "groq";
|
|
235
|
+
|
|
236
|
+
const model = firstInferenceEnv([
|
|
237
|
+
"LATTICES_VOICE_MODEL",
|
|
238
|
+
"LATTICES_HANDSOFF_MODEL",
|
|
239
|
+
"LATTICES_INFER_MODEL",
|
|
240
|
+
]) ?? VOICE_DEFAULT_MODELS[provider];
|
|
241
|
+
|
|
242
|
+
return { provider, model };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ── Provider factory ───────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
function getModel(provider: ProviderName, modelId: string) {
|
|
248
|
+
const creds = loadCredentials();
|
|
249
|
+
|
|
250
|
+
switch (provider) {
|
|
251
|
+
case "groq": {
|
|
252
|
+
const groq = createOpenAI({
|
|
253
|
+
baseURL: "https://api.groq.com/openai/v1",
|
|
254
|
+
apiKey: creds.groq,
|
|
255
|
+
});
|
|
256
|
+
return groq(modelId);
|
|
257
|
+
}
|
|
258
|
+
case "openai": {
|
|
259
|
+
const openai = createOpenAI({ apiKey: creds.openai });
|
|
260
|
+
return openai(modelId);
|
|
261
|
+
}
|
|
262
|
+
case "anthropic": {
|
|
263
|
+
const anthropic = createAnthropic({ apiKey: creds.anthropic });
|
|
264
|
+
return anthropic(modelId);
|
|
265
|
+
}
|
|
266
|
+
case "google": {
|
|
267
|
+
const google = createGoogleGenerativeAI({ apiKey: creds.google });
|
|
268
|
+
return google(modelId);
|
|
269
|
+
}
|
|
270
|
+
case "xai": {
|
|
271
|
+
const xai = createXai({ apiKey: creds.xai });
|
|
272
|
+
return xai(modelId);
|
|
273
|
+
}
|
|
274
|
+
case "minimax": {
|
|
275
|
+
// MiniMax uses OpenAI-compatible chat completions API
|
|
276
|
+
const minimax = createOpenAI({
|
|
277
|
+
baseURL: "https://api.minimax.io/v1",
|
|
278
|
+
apiKey: creds.minimax,
|
|
279
|
+
});
|
|
280
|
+
return minimax.chat(modelId);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// ── Logging ────────────────────────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
function log(tag: string, msg: string) {
|
|
288
|
+
const ts = new Date().toISOString().slice(11, 23);
|
|
289
|
+
console.error(`[${ts}] infer${tag ? `/${tag}` : ""}: ${msg}`);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ── Main inference function ────────────────────────────────────────
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Run inference against any supported provider.
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* // Simple
|
|
299
|
+
* const { text } = await infer("What windows do I have?", { provider: "groq" })
|
|
300
|
+
*
|
|
301
|
+
* // With system prompt and messages
|
|
302
|
+
* const { text } = await infer("tile chrome left", {
|
|
303
|
+
* provider: "groq",
|
|
304
|
+
* system: "You are a workspace assistant...",
|
|
305
|
+
* tag: "hands-off",
|
|
306
|
+
* })
|
|
307
|
+
*
|
|
308
|
+
* // With conversation history
|
|
309
|
+
* const { text } = await infer("now the other one right", {
|
|
310
|
+
* provider: "groq",
|
|
311
|
+
* messages: [
|
|
312
|
+
* { role: "user", content: "tile chrome left" },
|
|
313
|
+
* { role: "assistant", content: '{"actions":[...]}' },
|
|
314
|
+
* ],
|
|
315
|
+
* })
|
|
316
|
+
*/
|
|
317
|
+
export async function infer(
|
|
318
|
+
prompt: string,
|
|
319
|
+
options: InferOptions = {}
|
|
320
|
+
): Promise<InferResult> {
|
|
321
|
+
const provider = options.provider ?? "groq";
|
|
322
|
+
const modelId = options.model ?? DEFAULT_MODELS[provider];
|
|
323
|
+
const tag = options.tag ?? "";
|
|
324
|
+
|
|
325
|
+
// Check credentials
|
|
326
|
+
const creds = loadCredentials();
|
|
327
|
+
if (!creds[provider]) {
|
|
328
|
+
throw new Error(
|
|
329
|
+
`No API key for provider "${provider}". Set it in env, .env.local, ~/.lattices/inference.json, or ~/.config/speakeasy/settings.json`
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const model = getModel(provider, modelId);
|
|
334
|
+
|
|
335
|
+
// Build messages
|
|
336
|
+
const messages: ModelMessage[] = [
|
|
337
|
+
...(options.messages ?? []),
|
|
338
|
+
{ role: "user", content: prompt },
|
|
339
|
+
];
|
|
340
|
+
|
|
341
|
+
log(tag, `→ ${provider}/${modelId} (${prompt.length} chars)`);
|
|
342
|
+
const start = performance.now();
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const result = await generateText({
|
|
346
|
+
model,
|
|
347
|
+
system: options.system,
|
|
348
|
+
messages,
|
|
349
|
+
temperature: options.temperature ?? 0.3,
|
|
350
|
+
maxOutputTokens: options.maxTokens ?? 1024,
|
|
351
|
+
abortSignal: options.abortSignal,
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
const durationMs = Math.round(performance.now() - start);
|
|
355
|
+
|
|
356
|
+
const usage = result.usage
|
|
357
|
+
? {
|
|
358
|
+
promptTokens: result.usage.inputTokens,
|
|
359
|
+
completionTokens: result.usage.outputTokens,
|
|
360
|
+
totalTokens: result.usage.totalTokens,
|
|
361
|
+
}
|
|
362
|
+
: undefined;
|
|
363
|
+
|
|
364
|
+
log(
|
|
365
|
+
tag,
|
|
366
|
+
`← ${durationMs}ms | ${usage?.totalTokens ?? "?"} tokens | ${result.text.length} chars`
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
return {
|
|
370
|
+
text: result.text,
|
|
371
|
+
provider,
|
|
372
|
+
model: modelId,
|
|
373
|
+
durationMs,
|
|
374
|
+
usage,
|
|
375
|
+
};
|
|
376
|
+
} catch (err: any) {
|
|
377
|
+
const durationMs = Math.round(performance.now() - start);
|
|
378
|
+
log(tag, `✗ ${durationMs}ms | ${err.message ?? err}`);
|
|
379
|
+
throw err;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// ── Convenience: infer with automatic JSON parsing ─────────────────
|
|
384
|
+
|
|
385
|
+
export async function inferJSON<T = any>(
|
|
386
|
+
prompt: string,
|
|
387
|
+
options: InferOptions = {}
|
|
388
|
+
): Promise<{ data: T; raw: InferResult }> {
|
|
389
|
+
const result = await infer(prompt, options);
|
|
390
|
+
|
|
391
|
+
// Extract JSON from response (handle markdown fences)
|
|
392
|
+
let cleaned = result.text
|
|
393
|
+
.replace(/```json\s*/g, "")
|
|
394
|
+
.replace(/```\s*/g, "")
|
|
395
|
+
.trim();
|
|
396
|
+
|
|
397
|
+
const start = cleaned.indexOf("{");
|
|
398
|
+
const end = cleaned.lastIndexOf("}");
|
|
399
|
+
if (start === -1 || end === -1) {
|
|
400
|
+
throw new Error(`No JSON found in response: ${result.text.slice(0, 200)}`);
|
|
401
|
+
}
|
|
402
|
+
cleaned = cleaned.slice(start, end + 1);
|
|
403
|
+
|
|
404
|
+
const data = JSON.parse(cleaned) as T;
|
|
405
|
+
return { data, raw: result };
|
|
406
|
+
}
|
package/bin/lattices-app.ts
CHANGED
|
@@ -8,15 +8,15 @@ import { get } from "node:https";
|
|
|
8
8
|
import type { IncomingMessage } from "node:http";
|
|
9
9
|
|
|
10
10
|
const __dirname = import.meta.dir;
|
|
11
|
-
const appDir = resolve(__dirname, "../
|
|
11
|
+
const appDir = resolve(__dirname, "../apps/mac");
|
|
12
12
|
const cliRoot = resolve(__dirname, "..");
|
|
13
13
|
const bundlePath = resolve(appDir, "Lattices.app");
|
|
14
14
|
const binaryDir = resolve(bundlePath, "Contents/MacOS");
|
|
15
15
|
const binaryPath = resolve(binaryDir, "Lattices");
|
|
16
|
-
const entitlementsPath = resolve(__dirname, "../
|
|
16
|
+
const entitlementsPath = resolve(__dirname, "../apps/mac/Lattices.entitlements");
|
|
17
17
|
const resourcesDir = resolve(bundlePath, "Contents/Resources");
|
|
18
18
|
const iconPath = resolve(__dirname, "../assets/AppIcon.icns");
|
|
19
|
-
const tapSoundPath = resolve(__dirname, "../
|
|
19
|
+
const tapSoundPath = resolve(__dirname, "../apps/mac/Resources/tap.wav");
|
|
20
20
|
|
|
21
21
|
const REPO = "arach/lattices";
|
|
22
22
|
const RELEASE_APP_ASSET_NAMES = ["Lattices.dmg"];
|
|
@@ -68,6 +68,27 @@ function packageVersion(): string {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
function gitRevision(): string {
|
|
72
|
+
try {
|
|
73
|
+
return execSync("git rev-parse --short HEAD", {
|
|
74
|
+
cwd: cliRoot,
|
|
75
|
+
encoding: "utf8",
|
|
76
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
77
|
+
}).trim();
|
|
78
|
+
} catch {
|
|
79
|
+
return "unknown";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function xmlEscape(value: string): string {
|
|
84
|
+
return value
|
|
85
|
+
.replaceAll("&", "&")
|
|
86
|
+
.replaceAll("<", "<")
|
|
87
|
+
.replaceAll(">", ">")
|
|
88
|
+
.replaceAll('"', """)
|
|
89
|
+
.replaceAll("'", "'");
|
|
90
|
+
}
|
|
91
|
+
|
|
71
92
|
function launch(extraArgs: string[] = []): void {
|
|
72
93
|
if (isRunning()) {
|
|
73
94
|
console.log("lattices app is already running.");
|
|
@@ -133,9 +154,35 @@ function signBundle(): void {
|
|
|
133
154
|
} catch {}
|
|
134
155
|
}
|
|
135
156
|
|
|
136
|
-
|
|
157
|
+
type BundleBuildMetadata = {
|
|
158
|
+
channel?: "dev" | "release";
|
|
159
|
+
track?: string;
|
|
160
|
+
revision?: string;
|
|
161
|
+
timestamp?: string;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
function buildMetadataPlist(metadata: BundleBuildMetadata): string {
|
|
165
|
+
if (metadata.channel !== "dev") return "";
|
|
166
|
+
|
|
167
|
+
const track = metadata.track ?? "latest";
|
|
168
|
+
const revision = metadata.revision ?? gitRevision();
|
|
169
|
+
const timestamp = metadata.timestamp ?? new Date().toISOString();
|
|
170
|
+
|
|
171
|
+
return ` <key>LatticesBuildChannel</key>
|
|
172
|
+
<string>dev</string>
|
|
173
|
+
<key>LatticesBuildTrack</key>
|
|
174
|
+
<string>${xmlEscape(track)}</string>
|
|
175
|
+
<key>LatticesBuildRevision</key>
|
|
176
|
+
<string>${xmlEscape(revision)}</string>
|
|
177
|
+
<key>LatticesBuildTimestamp</key>
|
|
178
|
+
<string>${xmlEscape(timestamp)}</string>
|
|
179
|
+
`;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function writeInfoPlist(metadata: BundleBuildMetadata = {}): void {
|
|
137
183
|
mkdirSync(resolve(bundlePath, "Contents"), { recursive: true });
|
|
138
184
|
const version = packageVersion();
|
|
185
|
+
const buildMetadata = buildMetadataPlist(metadata);
|
|
139
186
|
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
140
187
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
141
188
|
<plist version="1.0">
|
|
@@ -167,7 +214,7 @@ function writeInfoPlist(): void {
|
|
|
167
214
|
<string>${version}</string>
|
|
168
215
|
<key>CFBundleShortVersionString</key>
|
|
169
216
|
<string>${version}</string>
|
|
170
|
-
<key>LSMinimumSystemVersion</key>
|
|
217
|
+
${buildMetadata} <key>LSMinimumSystemVersion</key>
|
|
171
218
|
<string>13.0</string>
|
|
172
219
|
<key>LSUIElement</key>
|
|
173
220
|
<true/>
|
|
@@ -209,7 +256,7 @@ function buildFromSource(): boolean {
|
|
|
209
256
|
|
|
210
257
|
mkdirSync(binaryDir, { recursive: true });
|
|
211
258
|
execSync(`cp '${builtPath}' '${binaryPath}'`);
|
|
212
|
-
writeInfoPlist();
|
|
259
|
+
writeInfoPlist({ channel: "dev", track: "latest" });
|
|
213
260
|
syncBundleResources();
|
|
214
261
|
|
|
215
262
|
// Re-sign the bundle so macOS TCC recognizes a stable identity across rebuilds.
|
|
@@ -381,7 +428,10 @@ if (cmd === "build") {
|
|
|
381
428
|
console.error("Swift is required. Install with: xcode-select --install");
|
|
382
429
|
process.exit(1);
|
|
383
430
|
}
|
|
384
|
-
buildFromSource()
|
|
431
|
+
if (!buildFromSource()) {
|
|
432
|
+
console.error("Build failed.");
|
|
433
|
+
process.exit(1);
|
|
434
|
+
}
|
|
385
435
|
} else if (cmd === "quit") {
|
|
386
436
|
if (quit()) {
|
|
387
437
|
console.log("lattices app stopped.");
|
package/bin/lattices-dev
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
set -euo pipefail
|
|
5
5
|
|
|
6
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")/../
|
|
7
|
+
APP_DIR="$(cd "$(dirname "$SCRIPT_PATH")/../apps/mac" && pwd)"
|
|
8
8
|
ROOT="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)"
|
|
9
9
|
LOG_FILE="$HOME/.lattices/lattices.log"
|
|
10
10
|
BINARY="$APP_DIR/.build/release/Lattices"
|
|
@@ -15,6 +15,8 @@ ENTITLEMENTS="$APP_DIR/Lattices.entitlements"
|
|
|
15
15
|
ICON="$ROOT/assets/AppIcon.icns"
|
|
16
16
|
TAP_SOUND="$APP_DIR/Resources/tap.wav"
|
|
17
17
|
VERSION="$(node -p "require('$ROOT/package.json').version" 2>/dev/null || echo '0.1.0')"
|
|
18
|
+
GIT_REVISION="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo 'unknown')"
|
|
19
|
+
BUILD_TIMESTAMP="$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
18
20
|
|
|
19
21
|
red() { printf "\033[31m%s\033[0m\n" "$*"; }
|
|
20
22
|
green() { printf "\033[32m%s\033[0m\n" "$*"; }
|
|
@@ -93,6 +95,14 @@ write_info_plist() {
|
|
|
93
95
|
<string>$VERSION</string>
|
|
94
96
|
<key>CFBundleShortVersionString</key>
|
|
95
97
|
<string>$VERSION</string>
|
|
98
|
+
<key>LatticesBuildChannel</key>
|
|
99
|
+
<string>dev</string>
|
|
100
|
+
<key>LatticesBuildTrack</key>
|
|
101
|
+
<string>latest</string>
|
|
102
|
+
<key>LatticesBuildRevision</key>
|
|
103
|
+
<string>$GIT_REVISION</string>
|
|
104
|
+
<key>LatticesBuildTimestamp</key>
|
|
105
|
+
<string>$BUILD_TIMESTAMP</string>
|
|
96
106
|
<key>LSMinimumSystemVersion</key>
|
|
97
107
|
<string>13.0</string>
|
|
98
108
|
<key>LSUIElement</key>
|
|
@@ -177,6 +187,31 @@ cmd_clear_logs() {
|
|
|
177
187
|
fi
|
|
178
188
|
}
|
|
179
189
|
|
|
190
|
+
cmd_link() {
|
|
191
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
192
|
+
red "bun not found. Install: curl -fsSL https://bun.sh/install | bash"
|
|
193
|
+
exit 1
|
|
194
|
+
fi
|
|
195
|
+
cd "$ROOT"
|
|
196
|
+
bun link
|
|
197
|
+
bun link @lattices/cli
|
|
198
|
+
if command -v lattices >/dev/null 2>&1; then
|
|
199
|
+
green "Linked. \`lattices\` -> $(command -v lattices)"
|
|
200
|
+
else
|
|
201
|
+
red "Linked, but \`lattices\` is not on PATH. Add bun's global bin to PATH:"
|
|
202
|
+
dim " export PATH=\"\$HOME/.bun/bin:\$PATH\""
|
|
203
|
+
fi
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
cmd_unlink() {
|
|
207
|
+
if ! command -v bun >/dev/null 2>&1; then
|
|
208
|
+
red "bun not found."
|
|
209
|
+
exit 1
|
|
210
|
+
fi
|
|
211
|
+
bun remove -g @lattices/cli >/dev/null 2>&1 || true
|
|
212
|
+
green "Unlinked."
|
|
213
|
+
}
|
|
214
|
+
|
|
180
215
|
cmd_status() {
|
|
181
216
|
if pgrep -x Lattices >/dev/null 2>&1; then
|
|
182
217
|
local pid=$(pgrep -x Lattices)
|
|
@@ -198,6 +233,8 @@ cmd_status() {
|
|
|
198
233
|
cmd_help() {
|
|
199
234
|
echo "lattices-dev — Lattices development commands"
|
|
200
235
|
echo ""
|
|
236
|
+
echo " link Symlink \`lattices\` to this checkout (bun link)"
|
|
237
|
+
echo " unlink Remove the bun link"
|
|
201
238
|
echo " restart Quit + rebuild + relaunch"
|
|
202
239
|
echo " build Build release binary"
|
|
203
240
|
echo " quit Stop the running app"
|
|
@@ -210,6 +247,8 @@ cmd_help() {
|
|
|
210
247
|
}
|
|
211
248
|
|
|
212
249
|
case "${1:-help}" in
|
|
250
|
+
link) cmd_link ;;
|
|
251
|
+
unlink) cmd_unlink ;;
|
|
213
252
|
restart) cmd_restart ;;
|
|
214
253
|
build) cmd_build ;;
|
|
215
254
|
quit|stop) cmd_quit ;;
|
package/bin/lattices.ts
CHANGED
|
@@ -549,7 +549,7 @@ function resolvePanes(dir: string): PaneConfig[] {
|
|
|
549
549
|
|
|
550
550
|
function detectProjectType(dir: string): string | null {
|
|
551
551
|
// Check for lattices-style hybrid project (Swift app + Node CLI)
|
|
552
|
-
if (existsSync(resolve(dir, "
|
|
552
|
+
if (existsSync(resolve(dir, "apps/mac/Package.swift")) && existsSync(resolve(dir, "bin/lattices-app.ts")))
|
|
553
553
|
return "lattices-app";
|
|
554
554
|
if (existsSync(resolve(dir, "Package.swift"))) return "swift";
|
|
555
555
|
if (existsSync(resolve(dir, "Cargo.toml"))) return "rust";
|
|
@@ -443,10 +443,10 @@ Those existing RPC names can remain stable while their internals are replaced.
|
|
|
443
443
|
|
|
444
444
|
Files likely involved:
|
|
445
445
|
|
|
446
|
-
- `
|
|
447
|
-
- new planner/executor files under `
|
|
448
|
-
- `
|
|
449
|
-
- `
|
|
446
|
+
- `apps/mac/Sources/LatticesApi.swift`
|
|
447
|
+
- new planner/executor files under `apps/mac/Sources/`
|
|
448
|
+
- `apps/mac/Sources/WindowTiler.swift`
|
|
449
|
+
- `apps/mac/Sources/WorkspaceManager.swift`
|
|
450
450
|
|
|
451
451
|
Deliverables:
|
|
452
452
|
|
|
@@ -474,9 +474,9 @@ Deliverables:
|
|
|
474
474
|
|
|
475
475
|
Files likely involved:
|
|
476
476
|
|
|
477
|
-
- `
|
|
478
|
-
- `
|
|
479
|
-
- `
|
|
477
|
+
- `apps/mac/Sources/VoiceIntentResolver.swift`
|
|
478
|
+
- `apps/mac/Sources/IntentEngine.swift`
|
|
479
|
+
- `apps/mac/Sources/HandsOffSession.swift`
|
|
480
480
|
|
|
481
481
|
Deliverables:
|
|
482
482
|
|
|
@@ -488,8 +488,8 @@ Deliverables:
|
|
|
488
488
|
|
|
489
489
|
Files likely involved:
|
|
490
490
|
|
|
491
|
-
- `
|
|
492
|
-
- `
|
|
491
|
+
- `apps/mac/Sources/HUDController.swift`
|
|
492
|
+
- `apps/mac/Sources/PaletteCommand.swift`
|
|
493
493
|
|
|
494
494
|
Deliverables:
|
|
495
495
|
|