@lattices/cli 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -4
- package/apps/mac/Info.plist +4 -2
- package/apps/mac/Lattices.app/Contents/Info.plist +4 -2
- package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/apps/mac/Lattices.app/Contents/Resources/docs/assistant-knowledge.md +130 -0
- package/apps/mac/Lattices.app/Contents/_CodeSignature/CodeResources +11 -0
- package/apps/mac/Lattices.entitlements +6 -0
- package/bin/assistant-intelligence.ts +41 -3
- package/bin/cli/capture.ts +252 -0
- package/bin/cli/daemon.ts +22 -0
- package/bin/cli/helpers.ts +105 -0
- package/bin/cli/layer.ts +178 -0
- package/bin/cli/runs.ts +43 -0
- package/bin/cli/search.ts +141 -0
- package/bin/cli/session.ts +32 -0
- package/bin/client.ts +2 -1
- package/bin/cua.ts +26 -0
- package/bin/infer.ts +22 -4
- package/bin/keychain.ts +75 -0
- package/bin/lattices-app.ts +111 -12
- package/bin/lattices-build-env.ts +77 -0
- package/bin/lattices-dev +29 -2
- package/bin/lattices.ts +729 -769
- package/docs/api.md +496 -3
- package/docs/app.md +5 -4
- package/docs/assistant-knowledge.md +130 -0
- package/docs/config.md +5 -0
- package/docs/hyperspace-grid-snappiness.md +210 -0
- package/docs/layers.md +53 -0
- package/docs/mouse-gestures.md +40 -3
- package/docs/ocr.md +3 -0
- package/docs/prompts/hands-off-system.md +9 -1
- package/docs/proposals/LAT-006-followup-gaps.md +103 -0
- package/docs/proposals/{LAT-006-mira-in-lattices.md → LAT-006-runs-and-capture-in-lattices.md} +83 -70
- package/docs/quickstart.md +3 -1
- package/docs/reference/dewey.config.ts +1 -1
- package/docs/release.md +4 -3
- package/docs/terminal-kit.md +87 -0
- package/docs/tiling-reference.md +5 -3
- package/docs/voice.md +3 -3
- package/package.json +27 -5
- package/packages/npm/sdk/cua.d.mts +1 -0
- package/packages/npm/sdk/cua.d.ts +188 -0
- package/packages/npm/sdk/cua.mjs +376 -0
package/bin/lattices-app.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { tmpdir } from "node:os";
|
|
|
6
6
|
import { join, resolve } from "node:path";
|
|
7
7
|
import { get } from "node:https";
|
|
8
8
|
import type { IncomingMessage } from "node:http";
|
|
9
|
+
import { resolveBuildEnv } from "./lattices-build-env";
|
|
9
10
|
|
|
10
11
|
const __dirname = import.meta.dir;
|
|
11
12
|
const appDir = resolve(__dirname, "../apps/mac");
|
|
@@ -37,6 +38,50 @@ function isRunning(): boolean {
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
type RunningProcess = { pid: string; command: string };
|
|
42
|
+
|
|
43
|
+
function runningLatticesProcesses(): RunningProcess[] {
|
|
44
|
+
let pids: string[];
|
|
45
|
+
try {
|
|
46
|
+
pids = execFileSync("pgrep", ["-x", "Lattices"], {
|
|
47
|
+
encoding: "utf8",
|
|
48
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
49
|
+
}).trim().split(/\s+/).filter(Boolean);
|
|
50
|
+
} catch {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const processes: RunningProcess[] = [];
|
|
55
|
+
for (const pid of pids) {
|
|
56
|
+
try {
|
|
57
|
+
const command = execFileSync("ps", ["-p", pid, "-o", "command="], {
|
|
58
|
+
encoding: "utf8",
|
|
59
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
60
|
+
}).trim();
|
|
61
|
+
if (command) processes.push({ pid, command });
|
|
62
|
+
} catch {}
|
|
63
|
+
}
|
|
64
|
+
return processes;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function runningBundleProcesses(targetBundlePath = bundlePath): RunningProcess[] {
|
|
68
|
+
const executable = resolve(targetBundlePath, "Contents/MacOS/Lattices");
|
|
69
|
+
return runningLatticesProcesses().filter(({ command }) =>
|
|
70
|
+
command === executable || command.startsWith(`${executable} `)
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function requireBundleNotRunningForBuild(): void {
|
|
75
|
+
const running = runningBundleProcesses();
|
|
76
|
+
if (!running.length) return;
|
|
77
|
+
|
|
78
|
+
console.error("Refusing to rebuild Lattices.app while that bundle is running.");
|
|
79
|
+
console.error("Rewriting or re-signing a live Mach-O can make macOS kill it later with Code Signature Invalid.");
|
|
80
|
+
console.error(`Running PID(s): ${running.map((proc) => proc.pid).join(", ")}`);
|
|
81
|
+
console.error("Use `lattices app restart` to quit, rebuild, and relaunch, or run `lattices app quit` before `lattices app build`.");
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
40
85
|
function sleep(ms: number): void {
|
|
41
86
|
execFileSync("/bin/sleep", [(ms / 1000).toString()], { stdio: "ignore" });
|
|
42
87
|
}
|
|
@@ -213,34 +258,73 @@ function resolveSigningIdentity(): string | null {
|
|
|
213
258
|
}
|
|
214
259
|
}
|
|
215
260
|
|
|
261
|
+
function bundleTeamIdentifier(): string | null {
|
|
262
|
+
try {
|
|
263
|
+
const output = execSync(`codesign -dv '${bundlePath}' 2>&1`, { encoding: "utf8" });
|
|
264
|
+
const match = output.match(/TeamIdentifier=(.+)/);
|
|
265
|
+
const team = match?.[1]?.trim();
|
|
266
|
+
return team && team !== "not set" ? team : null;
|
|
267
|
+
} catch {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function runCodesign(args: string[]): void {
|
|
273
|
+
try {
|
|
274
|
+
execFileSync("codesign", args, { stdio: "pipe" });
|
|
275
|
+
} catch (error) {
|
|
276
|
+
const stderr = (error as { stderr?: Buffer | string }).stderr;
|
|
277
|
+
const detail = stderr ? `\n${String(stderr).trim()}` : "";
|
|
278
|
+
throw new Error(`codesign ${args.join(" ")}${detail}`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
216
282
|
function signBundle(): void {
|
|
217
283
|
const identity = resolveSigningIdentity();
|
|
218
|
-
const
|
|
284
|
+
const entitlements = existsSync(entitlementsPath) ? entitlementsPath : null;
|
|
219
285
|
const tempBinaryPath = `${binaryPath}.cstemp`;
|
|
220
286
|
|
|
221
287
|
try {
|
|
222
288
|
if (existsSync(tempBinaryPath)) rmSync(tempBinaryPath, { force: true });
|
|
223
289
|
} catch {}
|
|
224
290
|
|
|
291
|
+
const sign = (signer: string, label: string) => {
|
|
292
|
+
// Sign the Mach-O first, then the bundle — more reliable than --deep and
|
|
293
|
+
// keeps a stable TeamIdentifier so macOS TCC grants survive rebuilds.
|
|
294
|
+
const binaryArgs = ["--force", "--options", "runtime", "--sign", signer];
|
|
295
|
+
if (entitlements) binaryArgs.push("--entitlements", entitlements);
|
|
296
|
+
binaryArgs.push(binaryPath);
|
|
297
|
+
runCodesign(binaryArgs);
|
|
298
|
+
|
|
299
|
+
const bundleArgs = ["--force", "--options", "runtime", "--sign", signer];
|
|
300
|
+
if (entitlements) bundleArgs.push("--entitlements", entitlements);
|
|
301
|
+
bundleArgs.push("--identifier", "dev.lattices.app", bundlePath);
|
|
302
|
+
runCodesign(bundleArgs);
|
|
303
|
+
|
|
304
|
+
const team = bundleTeamIdentifier();
|
|
305
|
+
if (team) {
|
|
306
|
+
console.log(`Signed (${label}) — TeamIdentifier=${team}`);
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
if (label === "ad-hoc") return;
|
|
310
|
+
throw new Error(`codesign reported no TeamIdentifier after ${label} signing`);
|
|
311
|
+
};
|
|
312
|
+
|
|
225
313
|
if (identity) {
|
|
226
314
|
console.log(`Signing with: ${identity}`);
|
|
227
315
|
try {
|
|
228
|
-
|
|
229
|
-
`codesign --force --options runtime --deep --sign '${identity}'${entFlag} --identifier dev.lattices.app '${bundlePath}'`,
|
|
230
|
-
{ stdio: "pipe" }
|
|
231
|
-
);
|
|
316
|
+
sign(identity, "developer");
|
|
232
317
|
return;
|
|
233
|
-
} catch {
|
|
234
|
-
console.log(`Warning: signing with '${identity}' failed —
|
|
318
|
+
} catch (error) {
|
|
319
|
+
console.log(`Warning: signing with '${identity}' failed — ${(error as Error).message}`);
|
|
320
|
+
console.log("Warning: falling back to ad-hoc. Privacy permissions will not persist across rebuilds.");
|
|
235
321
|
}
|
|
236
322
|
} else {
|
|
237
323
|
console.log("Warning: no local signing identity found — falling back to ad-hoc.");
|
|
324
|
+
console.log("Warning: grant Accessibility/Screen Recording again after each rebuild, or install a signing cert.");
|
|
238
325
|
}
|
|
239
326
|
|
|
240
|
-
|
|
241
|
-
`codesign --force --options runtime --deep --sign -${entFlag} --identifier dev.lattices.app '${bundlePath}'`,
|
|
242
|
-
{ stdio: "pipe" }
|
|
243
|
-
);
|
|
327
|
+
sign("-", "ad-hoc");
|
|
244
328
|
|
|
245
329
|
try {
|
|
246
330
|
if (existsSync(tempBinaryPath)) rmSync(tempBinaryPath, { force: true });
|
|
@@ -313,6 +397,8 @@ ${buildMetadata} <key>LSMinimumSystemVersion</key>
|
|
|
313
397
|
<true/>
|
|
314
398
|
<key>NSHighResolutionCapable</key>
|
|
315
399
|
<true/>
|
|
400
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
401
|
+
<string>Lattices uses the microphone for Hudson Voice dictation and voice commands.</string>
|
|
316
402
|
<key>NSSupportsAutomaticTermination</key>
|
|
317
403
|
<true/>
|
|
318
404
|
</dict>
|
|
@@ -329,6 +415,14 @@ function syncBundleResources(): void {
|
|
|
329
415
|
if (existsSync(tapSoundPath)) {
|
|
330
416
|
execSync(`cp '${tapSoundPath}' '${resolve(resourcesDir, "tap.wav")}'`);
|
|
331
417
|
}
|
|
418
|
+
// Bundle the assistant knowledge base so the in-app chat assistant can load it
|
|
419
|
+
// in shipped builds (dev builds fall back to the repo docs/ path).
|
|
420
|
+
const assistantDoc = resolve(cliRoot, "docs/assistant-knowledge.md");
|
|
421
|
+
if (existsSync(assistantDoc)) {
|
|
422
|
+
const docsDir = resolve(resourcesDir, "docs");
|
|
423
|
+
mkdirSync(docsDir, { recursive: true });
|
|
424
|
+
execSync(`cp '${assistantDoc}' '${resolve(docsDir, "assistant-knowledge.md")}'`);
|
|
425
|
+
}
|
|
332
426
|
}
|
|
333
427
|
|
|
334
428
|
// ── Build from source (current arch only) ────────────────────────────
|
|
@@ -339,6 +433,10 @@ function buildFromSource(): boolean {
|
|
|
339
433
|
execSync("swift build -c release", {
|
|
340
434
|
cwd: appDir,
|
|
341
435
|
stdio: "inherit",
|
|
436
|
+
// Build features are declared in apps/mac/build.json and resolved to the
|
|
437
|
+
// HUDSONKIT_WITH_* env HudsonKit gates on — one source of truth across
|
|
438
|
+
// every build entrypoint (see bin/lattices-build-env.ts).
|
|
439
|
+
env: { ...process.env, ...resolveBuildEnv() },
|
|
342
440
|
});
|
|
343
441
|
} catch {
|
|
344
442
|
return false;
|
|
@@ -466,7 +564,7 @@ async function ensureBinary(): Promise<void> {
|
|
|
466
564
|
console.error(
|
|
467
565
|
"Could not find a bundled lattices app or download one.\n" +
|
|
468
566
|
"Options:\n" +
|
|
469
|
-
" \u2022 Reinstall or update @lattices
|
|
567
|
+
" \u2022 Reinstall or update @arach/lattices\n" +
|
|
470
568
|
" \u2022 Developers can build from source with: lattices-app build\n" +
|
|
471
569
|
" \u2022 Download manually from: https://github.com/" + REPO + "/releases"
|
|
472
570
|
);
|
|
@@ -517,6 +615,7 @@ const shouldDetachUpdate = flags.includes("--detach");
|
|
|
517
615
|
const isUpdateWorker = flags.includes("--worker");
|
|
518
616
|
|
|
519
617
|
if (cmd === "build") {
|
|
618
|
+
requireBundleNotRunningForBuild();
|
|
520
619
|
if (!hasSwift()) {
|
|
521
620
|
console.error("Swift is required. Install with: xcode-select --install");
|
|
522
621
|
process.exit(1);
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* lattices-build-env — declarative build-feature resolver.
|
|
4
|
+
*
|
|
5
|
+
* Mirrors openscout's `hkit` style: an app names *features* in a manifest
|
|
6
|
+
* (apps/mac/build.json), never raw env vars. The feature → build-env mapping
|
|
7
|
+
* lives in the catalog below, so every build entrypoint (package / dev / dist)
|
|
8
|
+
* resolves the same way from one source of truth, instead of each hardcoding
|
|
9
|
+
* its own `HUDSONKIT_WITH_*` flags.
|
|
10
|
+
*
|
|
11
|
+
* import { resolveBuildEnv } from "./lattices-build-env"; // TS callers
|
|
12
|
+
* eval "$(bun bin/lattices-build-env.ts shell)" // bash callers
|
|
13
|
+
* bun bin/lattices-build-env.ts json // inspect
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
|
|
19
|
+
// Feature catalog: feature name -> build env HudsonKit gates on at SwiftPM
|
|
20
|
+
// manifest-eval time. HudsonVoice (Vox/Parakeet dictation) is an optional
|
|
21
|
+
// backend HudsonKit only declares when HUDSONKIT_WITH_VOICE=1 is set at build
|
|
22
|
+
// time. Name a *feature* here; never sprinkle the env var across build scripts.
|
|
23
|
+
export const FEATURE_CATALOG: Record<string, { env: Record<string, string>; note: string }> = {
|
|
24
|
+
voice: { env: { HUDSONKIT_WITH_VOICE: "1" }, note: "HudsonVoice — Vox/Parakeet dictation" },
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface BuildManifest {
|
|
28
|
+
app?: string;
|
|
29
|
+
features?: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const MANIFEST_PATH = join(import.meta.dir, "../apps/mac/build.json");
|
|
33
|
+
|
|
34
|
+
export function loadManifest(path = MANIFEST_PATH): BuildManifest {
|
|
35
|
+
if (!existsSync(path)) return {};
|
|
36
|
+
return JSON.parse(readFileSync(path, "utf8")) as BuildManifest;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function resolveFeatureEnv(features: string[] = []): Record<string, string> {
|
|
40
|
+
const env: Record<string, string> = {};
|
|
41
|
+
for (const f of features) {
|
|
42
|
+
const entry = FEATURE_CATALOG[f];
|
|
43
|
+
if (!entry) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`unknown build feature "${f}" — known features: ${Object.keys(FEATURE_CATALOG).join(", ")}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
Object.assign(env, entry.env);
|
|
49
|
+
}
|
|
50
|
+
return env;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Resolve the manifest's declared features into a build-env map. */
|
|
54
|
+
export function resolveBuildEnv(manifestPath?: string): Record<string, string> {
|
|
55
|
+
const features = loadManifest(manifestPath).features ?? [];
|
|
56
|
+
const env = resolveFeatureEnv(features);
|
|
57
|
+
// HudsonKit enables HudsonVoice by default; opt out unless the voice feature is declared.
|
|
58
|
+
if (!features.includes("voice")) {
|
|
59
|
+
env.HUDSONKIT_WITH_VOICE = "0";
|
|
60
|
+
}
|
|
61
|
+
return env;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// --- CLI: emit the resolved env for shell / json consumers -------------------
|
|
65
|
+
if (import.meta.main) {
|
|
66
|
+
const mode = process.argv[2] ?? "shell";
|
|
67
|
+
const env = resolveBuildEnv();
|
|
68
|
+
if (mode === "json") {
|
|
69
|
+
console.log(JSON.stringify(env, null, 2));
|
|
70
|
+
} else if (mode === "shell") {
|
|
71
|
+
// eval-able by bash: `eval "$(bun bin/lattices-build-env.ts shell)"`
|
|
72
|
+
for (const [k, v] of Object.entries(env)) console.log(`export ${k}=${JSON.stringify(v)}`);
|
|
73
|
+
} else {
|
|
74
|
+
console.error(`lattices-build-env: unknown mode "${mode}" (use: shell | json)`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/bin/lattices-dev
CHANGED
|
@@ -105,8 +105,9 @@ clear_lattices_drag_cache() {
|
|
|
105
105
|
done
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
|
|
109
|
-
local
|
|
108
|
+
is_bundle_running() {
|
|
109
|
+
local bundle="$1"
|
|
110
|
+
local target="$bundle/Contents/MacOS/Lattices"
|
|
110
111
|
local pid args
|
|
111
112
|
|
|
112
113
|
while IFS= read -r pid; do
|
|
@@ -120,6 +121,26 @@ is_install_bundle_running() {
|
|
|
120
121
|
return 1
|
|
121
122
|
}
|
|
122
123
|
|
|
124
|
+
is_install_bundle_running() {
|
|
125
|
+
is_bundle_running "$INSTALL_BUNDLE"
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
require_build_targets_not_running() {
|
|
129
|
+
if is_bundle_running "$BUNDLE"; then
|
|
130
|
+
red "Refusing to rebuild the repo-local app artifact while it is running."
|
|
131
|
+
dim "Rewriting or re-signing a live Mach-O can make macOS kill it later with Code Signature Invalid."
|
|
132
|
+
dim "Use \`lattices-dev restart\` to quit, rebuild, and relaunch."
|
|
133
|
+
exit 1
|
|
134
|
+
fi
|
|
135
|
+
|
|
136
|
+
if is_install_bundle_running; then
|
|
137
|
+
red "Refusing to install over the running dev app bundle."
|
|
138
|
+
dim "Rewriting or re-signing a live Mach-O can make macOS kill it later with Code Signature Invalid."
|
|
139
|
+
dim "Use \`lattices-dev restart\` or \`lattices app restart\` to quit, rebuild, and relaunch."
|
|
140
|
+
exit 1
|
|
141
|
+
fi
|
|
142
|
+
}
|
|
143
|
+
|
|
123
144
|
write_info_plist() {
|
|
124
145
|
mkdir -p "$BUNDLE/Contents"
|
|
125
146
|
cat > "$BUNDLE/Contents/Info.plist" <<PLIST
|
|
@@ -168,6 +189,8 @@ write_info_plist() {
|
|
|
168
189
|
<true/>
|
|
169
190
|
<key>NSHighResolutionCapable</key>
|
|
170
191
|
<true/>
|
|
192
|
+
<key>NSMicrophoneUsageDescription</key>
|
|
193
|
+
<string>Lattices uses the microphone for Hudson Voice dictation and voice commands.</string>
|
|
171
194
|
<key>NSSupportsAutomaticTermination</key>
|
|
172
195
|
<true/>
|
|
173
196
|
</dict>
|
|
@@ -177,6 +200,10 @@ PLIST
|
|
|
177
200
|
|
|
178
201
|
cmd_build() {
|
|
179
202
|
echo "Building dev app..."
|
|
203
|
+
require_build_targets_not_running
|
|
204
|
+
# Build features are declared in apps/mac/build.json and resolved to the
|
|
205
|
+
# HUDSONKIT_WITH_* env HudsonKit gates on (see bin/lattices-build-env.ts).
|
|
206
|
+
eval "$(bun "$ROOT/bin/lattices-build-env.ts" shell)"
|
|
180
207
|
cd "$APP_DIR" && swift build -c release
|
|
181
208
|
# Build into a repo-local dev artifact, then install to a stable app path
|
|
182
209
|
# that macOS permissions can trust across rebuilds.
|