@lattices/cli 0.5.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 +14 -5
- 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/proposals/LAT-007-unified-app-shell.md +128 -0
- package/docs/quickstart.md +3 -1
- package/docs/reference/dewey.config.ts +1 -1
- package/docs/release.md +4 -3
- package/docs/repo-structure.md +1 -0
- package/docs/terminal-kit.md +87 -0
- package/docs/tiling-reference.md +5 -3
- package/docs/voice.md +3 -3
- package/package.json +29 -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/docs/tiling-reference.md
CHANGED
|
@@ -62,11 +62,13 @@ All valid position strings that `TilePosition` accepts:
|
|
|
62
62
|
|
|
63
63
|
### Custom Grid Syntax
|
|
64
64
|
|
|
65
|
-
For arbitrary grids: `grid:CxR:C,R`
|
|
65
|
+
For arbitrary grids: compact `CxR:C,R` or canonical `grid:CxR:C,R`
|
|
66
66
|
|
|
67
67
|
- `C` = total columns, `R` = total rows
|
|
68
|
-
- `C,R`
|
|
69
|
-
-
|
|
68
|
+
- Compact `CxR:C,R` starts at 1 from the top-left
|
|
69
|
+
- Canonical `grid:CxR:C,R` starts at 0 for API/wire compatibility
|
|
70
|
+
- Example: `5x3:3,2` or `grid:5x3:2,1` = center cell of a 5×3 grid
|
|
71
|
+
- Spans can use two inclusive corners, such as `4x4:1,1-2,2`
|
|
70
72
|
|
|
71
73
|
Parsed by `PlacementSpec` / `parseGridString()` into fractional `(x, y, w, h)`.
|
|
72
74
|
|
package/docs/voice.md
CHANGED
|
@@ -4,7 +4,7 @@ description: Natural language voice control for window management
|
|
|
4
4
|
order: 7
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
Voice commands let you control Lattices by speaking. Press **Hyper+
|
|
7
|
+
Voice commands let you control Lattices by speaking. Press **Hyper+D**
|
|
8
8
|
to open the voice command window, hold **Option** to speak, release to
|
|
9
9
|
stop. Lattices transcribes your speech via Vox,
|
|
10
10
|
matches it to an intent, and executes it.
|
|
@@ -13,7 +13,7 @@ matches it to an intent, and executes it.
|
|
|
13
13
|
|
|
14
14
|
1. Install Vox (provides mic + transcription)
|
|
15
15
|
2. Connect an Assistant provider in **Settings > AI**
|
|
16
|
-
3. Press **Hyper+
|
|
16
|
+
3. Press **Hyper+D** to open the voice command window
|
|
17
17
|
4. Hold **Option** and speak a command
|
|
18
18
|
5. Release **Option** — Lattices transcribes and executes
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ matches it to an intent, and executes it.
|
|
|
21
21
|
|
|
22
22
|
| Key | Action |
|
|
23
23
|
|-----|--------|
|
|
24
|
-
| **Hyper+
|
|
24
|
+
| **Hyper+D** | Open/close voice command window |
|
|
25
25
|
| **⌥ (hold)** | Push-to-talk — hold to record, release to stop |
|
|
26
26
|
| **Tab** | Arm/disarm the mic |
|
|
27
27
|
| **Escape** | Cancel recording or dismiss window |
|
package/package.json
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lattices/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.6.1",
|
|
4
|
+
"description": "macOS workspace manager — menu bar app, CLI, and WebSocket daemon API for tiling, search, layers, and agent control",
|
|
5
|
+
"homepage": "https://lattices.dev",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/arach/lattices/issues"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public",
|
|
11
|
+
"provenance": true
|
|
12
|
+
},
|
|
5
13
|
"packageManager": "bun@1.3.11",
|
|
6
14
|
"engines": {
|
|
7
15
|
"node": ">=18"
|
|
@@ -11,13 +19,19 @@
|
|
|
11
19
|
"lattices-app": "./bin/lattices-app.ts"
|
|
12
20
|
},
|
|
13
21
|
"keywords": [
|
|
22
|
+
"lattices",
|
|
23
|
+
"macos",
|
|
24
|
+
"workspace",
|
|
25
|
+
"window-manager",
|
|
14
26
|
"tmux",
|
|
27
|
+
"daemon",
|
|
28
|
+
"ai-agents",
|
|
15
29
|
"claude",
|
|
16
|
-
"dev-server",
|
|
17
30
|
"developer-tools",
|
|
18
31
|
"cli",
|
|
19
32
|
"terminal",
|
|
20
|
-
"
|
|
33
|
+
"ocr",
|
|
34
|
+
"tiling"
|
|
21
35
|
],
|
|
22
36
|
"repository": {
|
|
23
37
|
"type": "git",
|
|
@@ -27,16 +41,21 @@
|
|
|
27
41
|
"exports": {
|
|
28
42
|
".": "./bin/client.ts",
|
|
29
43
|
"./assistant-intelligence": "./bin/assistant-intelligence.ts",
|
|
44
|
+
"./cua": "./bin/cua.ts",
|
|
30
45
|
"./daemon-client": "./bin/daemon-client.ts",
|
|
31
46
|
"./project-twin": "./bin/project-twin.ts"
|
|
32
47
|
},
|
|
33
48
|
"scripts": {
|
|
34
49
|
"dev": "bun --cwd apps/site dev",
|
|
35
50
|
"site:build": "bun --cwd apps/site build",
|
|
51
|
+
"studio:dev": "bun --cwd apps/studio dev",
|
|
52
|
+
"studio:build": "bun --cwd apps/studio build",
|
|
36
53
|
"docs:agent": "bun --cwd apps/site agent-docs",
|
|
37
54
|
"check": "bun run check:types && bun run check:app",
|
|
38
55
|
"check:types": "tsc --noEmit",
|
|
39
56
|
"check:app": "env CLANG_MODULE_CACHE_PATH=/tmp/lattices-clang-cache SWIFTPM_TESTS_MODULECACHE=/tmp/lattices-swiftpm-cache swift build --package-path apps/mac",
|
|
57
|
+
"test": "node --experimental-strip-types --test tests/cli.test.mjs",
|
|
58
|
+
"test:cli": "node --experimental-strip-types --test tests/cli.test.mjs",
|
|
40
59
|
"test:e2e": "node --experimental-strip-types --test tests/e2e-daemon.test.mjs",
|
|
41
60
|
"test:e2e:voice": "node --experimental-strip-types tests/eval-voice.js",
|
|
42
61
|
"typecheck": "bun run check:types",
|
|
@@ -47,9 +66,14 @@
|
|
|
47
66
|
"prepack": "bash ./bin/lattices-build package"
|
|
48
67
|
},
|
|
49
68
|
"type": "module",
|
|
50
|
-
"os": [
|
|
69
|
+
"os": [
|
|
70
|
+
"darwin"
|
|
71
|
+
],
|
|
51
72
|
"files": [
|
|
52
73
|
"bin",
|
|
74
|
+
"packages/npm/sdk/cua.mjs",
|
|
75
|
+
"packages/npm/sdk/cua.d.ts",
|
|
76
|
+
"packages/npm/sdk/cua.d.mts",
|
|
53
77
|
"apps/mac/Info.plist",
|
|
54
78
|
"apps/mac/Lattices.app",
|
|
55
79
|
"apps/mac/Lattices.entitlements",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./cua.d.ts";
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export type ComputerTreatment = "observe" | "stage" | "present" | "execute";
|
|
4
|
+
export type ComputerClickTransport =
|
|
5
|
+
| "auto"
|
|
6
|
+
| "ax"
|
|
7
|
+
| "accessibility"
|
|
8
|
+
| "pointer"
|
|
9
|
+
| "mouse"
|
|
10
|
+
| "hardware";
|
|
11
|
+
export type CursorStyle = "spotlight" | "pulse" | "marker";
|
|
12
|
+
export type CursorShape =
|
|
13
|
+
| "arrow"
|
|
14
|
+
| "chevron"
|
|
15
|
+
| "facet"
|
|
16
|
+
| "shard"
|
|
17
|
+
| "wedge"
|
|
18
|
+
| "prism"
|
|
19
|
+
| "notch"
|
|
20
|
+
| "needle"
|
|
21
|
+
| "petal"
|
|
22
|
+
| "kite";
|
|
23
|
+
export type CursorSize = "tiny" | "small" | "regular" | "large";
|
|
24
|
+
export type CursorTrail = "none" | "thread" | "ribbon" | "spark" | "comet" | "route";
|
|
25
|
+
export type CursorMotion =
|
|
26
|
+
| "glide"
|
|
27
|
+
| "snap"
|
|
28
|
+
| "float"
|
|
29
|
+
| "rush"
|
|
30
|
+
| "crawl"
|
|
31
|
+
| "accelerate"
|
|
32
|
+
| "teleport"
|
|
33
|
+
| "spring"
|
|
34
|
+
| "magnet"
|
|
35
|
+
| "slingshot";
|
|
36
|
+
export type CursorTrajectory = "straight" | "soft" | "arc" | "swoop" | "overshoot";
|
|
37
|
+
export type CursorGlow = "none" | "soft" | "halo" | "comet";
|
|
38
|
+
export type CursorIdle =
|
|
39
|
+
| "still"
|
|
40
|
+
| "breathe"
|
|
41
|
+
| "wiggle"
|
|
42
|
+
| "orbit"
|
|
43
|
+
| "hover"
|
|
44
|
+
| "nod"
|
|
45
|
+
| "drift"
|
|
46
|
+
| "shimmer"
|
|
47
|
+
| "blink"
|
|
48
|
+
| "tremble";
|
|
49
|
+
export type CursorEdge =
|
|
50
|
+
| "none"
|
|
51
|
+
| "pulse"
|
|
52
|
+
| "ripple"
|
|
53
|
+
| "tick"
|
|
54
|
+
| "reticle"
|
|
55
|
+
| "blink"
|
|
56
|
+
| "spark"
|
|
57
|
+
| "underline"
|
|
58
|
+
| "echo"
|
|
59
|
+
| "scan"
|
|
60
|
+
| "pin";
|
|
61
|
+
export type CursorSound = "none" | "tick" | "click" | "engage" | "chime";
|
|
62
|
+
export type CaptionPlacement =
|
|
63
|
+
| "top-left"
|
|
64
|
+
| "top-right"
|
|
65
|
+
| "bottom-left"
|
|
66
|
+
| "bottom-right"
|
|
67
|
+
| "top-center"
|
|
68
|
+
| "top"
|
|
69
|
+
| "center"
|
|
70
|
+
| "middle"
|
|
71
|
+
| "near-cursor"
|
|
72
|
+
| "cursor";
|
|
73
|
+
|
|
74
|
+
export interface WindowTarget {
|
|
75
|
+
wid?: number;
|
|
76
|
+
app?: string;
|
|
77
|
+
title?: string;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface ActionBase {
|
|
81
|
+
treatment?: ComputerTreatment;
|
|
82
|
+
dryRun?: boolean;
|
|
83
|
+
capture?: boolean;
|
|
84
|
+
source?: string;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface PointTarget {
|
|
88
|
+
x?: number;
|
|
89
|
+
y?: number;
|
|
90
|
+
xRatio?: number;
|
|
91
|
+
yRatio?: number;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export interface ComputerClickParams extends WindowTarget, PointTarget, ActionBase {
|
|
95
|
+
button?: "left" | "right" | "secondary" | "context";
|
|
96
|
+
transport?: ComputerClickTransport;
|
|
97
|
+
axLabel?: string;
|
|
98
|
+
targetText?: string;
|
|
99
|
+
noFocus?: boolean;
|
|
100
|
+
label?: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ComputerMagicCursorParams extends WindowTarget, PointTarget, ActionBase {
|
|
104
|
+
style?: CursorStyle;
|
|
105
|
+
appearance?: CursorStyle;
|
|
106
|
+
shape?: CursorShape;
|
|
107
|
+
angleDeg?: number;
|
|
108
|
+
size?: CursorSize;
|
|
109
|
+
color?: string;
|
|
110
|
+
durationMs?: number;
|
|
111
|
+
label?: string;
|
|
112
|
+
caption?: string;
|
|
113
|
+
captionTitle?: string;
|
|
114
|
+
captionBody?: string;
|
|
115
|
+
captionDetail?: string;
|
|
116
|
+
captionTags?: string;
|
|
117
|
+
captionMode?: "auto" | "selection";
|
|
118
|
+
captionEyebrow?: string;
|
|
119
|
+
captionLeadMs?: number;
|
|
120
|
+
captionSound?: CursorSound;
|
|
121
|
+
captionPlacement?: CaptionPlacement;
|
|
122
|
+
captionMargin?: number;
|
|
123
|
+
captionX?: number;
|
|
124
|
+
captionY?: number;
|
|
125
|
+
captionXRatio?: number;
|
|
126
|
+
captionYRatio?: number;
|
|
127
|
+
captionLeftRatio?: number;
|
|
128
|
+
captionTopRatio?: number;
|
|
129
|
+
sound?: CursorSound;
|
|
130
|
+
sfx?: CursorSound;
|
|
131
|
+
showCaption?: boolean;
|
|
132
|
+
captionSelections?: boolean;
|
|
133
|
+
treatmentLabel?: string;
|
|
134
|
+
variant?: string;
|
|
135
|
+
trail?: CursorTrail;
|
|
136
|
+
pathStyle?: CursorTrail;
|
|
137
|
+
motion?: CursorMotion;
|
|
138
|
+
trajectory?: CursorTrajectory;
|
|
139
|
+
glow?: CursorGlow;
|
|
140
|
+
bloom?: CursorGlow;
|
|
141
|
+
idle?: CursorIdle;
|
|
142
|
+
settle?: CursorIdle;
|
|
143
|
+
presence?: CursorIdle;
|
|
144
|
+
edge?: CursorEdge;
|
|
145
|
+
edgeEffect?: CursorEdge;
|
|
146
|
+
arrival?: CursorEdge;
|
|
147
|
+
typewriter?: boolean;
|
|
148
|
+
typing?: boolean;
|
|
149
|
+
typeIntervalMs?: number;
|
|
150
|
+
typingIntervalMs?: number;
|
|
151
|
+
text?: string;
|
|
152
|
+
append?: boolean;
|
|
153
|
+
fromX?: number;
|
|
154
|
+
fromY?: number;
|
|
155
|
+
fromXRatio?: number;
|
|
156
|
+
fromYRatio?: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export interface CuaClientOptions {
|
|
160
|
+
defaultTimeoutMs?: number;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export interface CuaClient {
|
|
164
|
+
click(params: ComputerClickParams): Promise<unknown>;
|
|
165
|
+
magicCursor(params: ComputerMagicCursorParams): Promise<unknown>;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export declare const computerTreatmentSchema: z.ZodType<ComputerTreatment>;
|
|
169
|
+
export declare const computerClickTransportSchema: z.ZodType<ComputerClickTransport>;
|
|
170
|
+
export declare const cursorStyleSchema: z.ZodType<CursorStyle>;
|
|
171
|
+
export declare const cursorShapeSchema: z.ZodType<CursorShape>;
|
|
172
|
+
export declare const cursorSizeSchema: z.ZodType<CursorSize>;
|
|
173
|
+
export declare const cursorTrailSchema: z.ZodType<CursorTrail>;
|
|
174
|
+
export declare const cursorMotionSchema: z.ZodType<CursorMotion>;
|
|
175
|
+
export declare const cursorTrajectorySchema: z.ZodType<CursorTrajectory>;
|
|
176
|
+
export declare const cursorGlowSchema: z.ZodType<CursorGlow>;
|
|
177
|
+
export declare const cursorIdleSchema: z.ZodType<CursorIdle>;
|
|
178
|
+
export declare const cursorEdgeSchema: z.ZodType<CursorEdge>;
|
|
179
|
+
export declare const cursorSoundSchema: z.ZodType<CursorSound>;
|
|
180
|
+
export declare const captionPlacementSchema: z.ZodType<CaptionPlacement>;
|
|
181
|
+
export declare const computerClickParamsSchema: z.ZodType<ComputerClickParams>;
|
|
182
|
+
export declare const computerMagicCursorParamsSchema: z.ZodType<ComputerMagicCursorParams>;
|
|
183
|
+
|
|
184
|
+
export declare function createCuaClient(options?: CuaClientOptions): CuaClient;
|
|
185
|
+
export declare function click(params: ComputerClickParams): Promise<unknown>;
|
|
186
|
+
export declare function magicCursor(params: ComputerMagicCursorParams): Promise<unknown>;
|
|
187
|
+
|
|
188
|
+
export declare const cua: CuaClient;
|
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { createConnection } from "node:net";
|
|
3
|
+
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
const DAEMON_HOST = "127.0.0.1";
|
|
7
|
+
const DAEMON_PORT = 9399;
|
|
8
|
+
|
|
9
|
+
export const computerTreatmentSchema = z.enum([
|
|
10
|
+
"observe",
|
|
11
|
+
"stage",
|
|
12
|
+
"present",
|
|
13
|
+
"execute",
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
export const computerClickTransportSchema = z.enum([
|
|
17
|
+
"auto",
|
|
18
|
+
"ax",
|
|
19
|
+
"accessibility",
|
|
20
|
+
"pointer",
|
|
21
|
+
"mouse",
|
|
22
|
+
"hardware",
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
export const cursorStyleSchema = z.enum(["spotlight", "pulse", "marker"]);
|
|
26
|
+
export const cursorShapeSchema = z.enum([
|
|
27
|
+
"arrow",
|
|
28
|
+
"chevron",
|
|
29
|
+
"facet",
|
|
30
|
+
"shard",
|
|
31
|
+
"wedge",
|
|
32
|
+
"prism",
|
|
33
|
+
"notch",
|
|
34
|
+
"needle",
|
|
35
|
+
"petal",
|
|
36
|
+
"kite",
|
|
37
|
+
]);
|
|
38
|
+
export const cursorSizeSchema = z.enum(["tiny", "small", "regular", "large"]);
|
|
39
|
+
export const cursorTrailSchema = z.enum(["none", "thread", "ribbon", "spark", "comet", "route"]);
|
|
40
|
+
export const cursorMotionSchema = z.enum([
|
|
41
|
+
"glide",
|
|
42
|
+
"snap",
|
|
43
|
+
"float",
|
|
44
|
+
"rush",
|
|
45
|
+
"crawl",
|
|
46
|
+
"accelerate",
|
|
47
|
+
"teleport",
|
|
48
|
+
"spring",
|
|
49
|
+
"magnet",
|
|
50
|
+
"slingshot",
|
|
51
|
+
]);
|
|
52
|
+
export const cursorTrajectorySchema = z.enum(["straight", "soft", "arc", "swoop", "overshoot"]);
|
|
53
|
+
export const cursorGlowSchema = z.enum(["none", "soft", "halo", "comet"]);
|
|
54
|
+
export const cursorIdleSchema = z.enum([
|
|
55
|
+
"still",
|
|
56
|
+
"breathe",
|
|
57
|
+
"wiggle",
|
|
58
|
+
"orbit",
|
|
59
|
+
"hover",
|
|
60
|
+
"nod",
|
|
61
|
+
"drift",
|
|
62
|
+
"shimmer",
|
|
63
|
+
"blink",
|
|
64
|
+
"tremble",
|
|
65
|
+
]);
|
|
66
|
+
export const cursorEdgeSchema = z.enum([
|
|
67
|
+
"none",
|
|
68
|
+
"pulse",
|
|
69
|
+
"ripple",
|
|
70
|
+
"tick",
|
|
71
|
+
"reticle",
|
|
72
|
+
"blink",
|
|
73
|
+
"spark",
|
|
74
|
+
"underline",
|
|
75
|
+
"echo",
|
|
76
|
+
"scan",
|
|
77
|
+
"pin",
|
|
78
|
+
]);
|
|
79
|
+
export const cursorSoundSchema = z.enum(["none", "tick", "click", "engage", "chime"]);
|
|
80
|
+
export const captionPlacementSchema = z.enum([
|
|
81
|
+
"top-left",
|
|
82
|
+
"top-right",
|
|
83
|
+
"bottom-left",
|
|
84
|
+
"bottom-right",
|
|
85
|
+
"top-center",
|
|
86
|
+
"top",
|
|
87
|
+
"center",
|
|
88
|
+
"middle",
|
|
89
|
+
"near-cursor",
|
|
90
|
+
"cursor",
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
const ratioSchema = z.number().min(0).max(1);
|
|
94
|
+
const pointSchema = z.number().finite();
|
|
95
|
+
|
|
96
|
+
const windowTargetSchema = z.object({
|
|
97
|
+
wid: z.number().int().positive().optional(),
|
|
98
|
+
app: z.string().min(1).optional(),
|
|
99
|
+
title: z.string().optional(),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const actionBaseSchema = z.object({
|
|
103
|
+
treatment: computerTreatmentSchema.optional(),
|
|
104
|
+
dryRun: z.boolean().optional(),
|
|
105
|
+
capture: z.boolean().optional(),
|
|
106
|
+
source: z.string().optional(),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const pointTargetSchema = z.object({
|
|
110
|
+
x: pointSchema.optional(),
|
|
111
|
+
y: pointSchema.optional(),
|
|
112
|
+
xRatio: ratioSchema.optional(),
|
|
113
|
+
yRatio: ratioSchema.optional(),
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
const cursorAppearanceSchema = z.object({
|
|
117
|
+
style: cursorStyleSchema.optional(),
|
|
118
|
+
appearance: cursorStyleSchema.optional(),
|
|
119
|
+
shape: cursorShapeSchema.optional(),
|
|
120
|
+
angleDeg: z.number().finite().optional(),
|
|
121
|
+
size: cursorSizeSchema.optional(),
|
|
122
|
+
color: z.string().min(1).optional(),
|
|
123
|
+
durationMs: z.number().int().positive().optional(),
|
|
124
|
+
label: z.string().optional(),
|
|
125
|
+
caption: z.string().optional(),
|
|
126
|
+
captionTitle: z.string().optional(),
|
|
127
|
+
captionBody: z.string().optional(),
|
|
128
|
+
captionDetail: z.string().optional(),
|
|
129
|
+
captionTags: z.string().optional(),
|
|
130
|
+
captionMode: z.enum(["auto", "selection"]).optional(),
|
|
131
|
+
captionEyebrow: z.string().optional(),
|
|
132
|
+
captionLeadMs: z.number().finite().nonnegative().optional(),
|
|
133
|
+
captionSound: cursorSoundSchema.optional(),
|
|
134
|
+
captionPlacement: captionPlacementSchema.optional(),
|
|
135
|
+
captionMargin: z.number().finite().nonnegative().optional(),
|
|
136
|
+
captionX: z.number().finite().optional(),
|
|
137
|
+
captionY: z.number().finite().optional(),
|
|
138
|
+
captionXRatio: ratioSchema.optional(),
|
|
139
|
+
captionYRatio: ratioSchema.optional(),
|
|
140
|
+
captionLeftRatio: ratioSchema.optional(),
|
|
141
|
+
captionTopRatio: ratioSchema.optional(),
|
|
142
|
+
sound: cursorSoundSchema.optional(),
|
|
143
|
+
sfx: cursorSoundSchema.optional(),
|
|
144
|
+
showCaption: z.boolean().optional(),
|
|
145
|
+
captionSelections: z.boolean().optional(),
|
|
146
|
+
treatmentLabel: z.string().optional(),
|
|
147
|
+
variant: z.string().optional(),
|
|
148
|
+
trail: cursorTrailSchema.optional(),
|
|
149
|
+
pathStyle: cursorTrailSchema.optional(),
|
|
150
|
+
motion: cursorMotionSchema.optional(),
|
|
151
|
+
trajectory: cursorTrajectorySchema.optional(),
|
|
152
|
+
glow: cursorGlowSchema.optional(),
|
|
153
|
+
bloom: cursorGlowSchema.optional(),
|
|
154
|
+
idle: cursorIdleSchema.optional(),
|
|
155
|
+
settle: cursorIdleSchema.optional(),
|
|
156
|
+
presence: cursorIdleSchema.optional(),
|
|
157
|
+
edge: cursorEdgeSchema.optional(),
|
|
158
|
+
edgeEffect: cursorEdgeSchema.optional(),
|
|
159
|
+
arrival: cursorEdgeSchema.optional(),
|
|
160
|
+
typewriter: z.boolean().optional(),
|
|
161
|
+
typing: z.boolean().optional(),
|
|
162
|
+
typeIntervalMs: z.number().finite().positive().optional(),
|
|
163
|
+
typingIntervalMs: z.number().finite().positive().optional(),
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
export const computerClickParamsSchema = windowTargetSchema
|
|
167
|
+
.merge(pointTargetSchema)
|
|
168
|
+
.merge(actionBaseSchema)
|
|
169
|
+
.merge(cursorAppearanceSchema.pick({ label: true }))
|
|
170
|
+
.extend({
|
|
171
|
+
button: z.enum(["left", "right", "secondary", "context"]).optional(),
|
|
172
|
+
transport: computerClickTransportSchema.optional(),
|
|
173
|
+
axLabel: z.string().min(1).optional(),
|
|
174
|
+
targetText: z.string().min(1).optional(),
|
|
175
|
+
noFocus: z.boolean().optional(),
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
export const computerMagicCursorParamsSchema = windowTargetSchema
|
|
179
|
+
.merge(pointTargetSchema)
|
|
180
|
+
.merge(actionBaseSchema)
|
|
181
|
+
.merge(cursorAppearanceSchema)
|
|
182
|
+
.extend({
|
|
183
|
+
text: z.string().optional(),
|
|
184
|
+
append: z.boolean().optional(),
|
|
185
|
+
fromX: pointSchema.optional(),
|
|
186
|
+
fromY: pointSchema.optional(),
|
|
187
|
+
fromXRatio: ratioSchema.optional(),
|
|
188
|
+
fromYRatio: ratioSchema.optional(),
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
export function createCuaClient(options = {}) {
|
|
192
|
+
const defaultTimeoutMs = options.defaultTimeoutMs ?? 30_000;
|
|
193
|
+
|
|
194
|
+
async function call(method, params, timeoutMs = defaultTimeoutMs) {
|
|
195
|
+
return daemonCall(method, params, timeoutMs);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
click(params) {
|
|
200
|
+
return call("computer.click", computerClickParamsSchema.parse(params));
|
|
201
|
+
},
|
|
202
|
+
magicCursor(params) {
|
|
203
|
+
return call("computer.magicCursor", computerMagicCursorParamsSchema.parse(params));
|
|
204
|
+
},
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export const cua = createCuaClient();
|
|
209
|
+
|
|
210
|
+
export function click(params) {
|
|
211
|
+
return cua.click(params);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function magicCursor(params) {
|
|
215
|
+
return cua.magicCursor(params);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function daemonCall(method, params = null, timeoutMs = 3000) {
|
|
219
|
+
const id = randomBytes(4).toString("hex");
|
|
220
|
+
const request = JSON.stringify({ id, method, params: params ?? null });
|
|
221
|
+
|
|
222
|
+
return new Promise((resolve, reject) => {
|
|
223
|
+
const socket = createConnection({ host: DAEMON_HOST, port: DAEMON_PORT });
|
|
224
|
+
let settled = false;
|
|
225
|
+
let buffer = Buffer.alloc(0);
|
|
226
|
+
let upgraded = false;
|
|
227
|
+
|
|
228
|
+
const timer = setTimeout(() => {
|
|
229
|
+
if (!settled) {
|
|
230
|
+
settled = true;
|
|
231
|
+
socket.destroy();
|
|
232
|
+
reject(new Error("Daemon request timed out"));
|
|
233
|
+
}
|
|
234
|
+
}, timeoutMs);
|
|
235
|
+
|
|
236
|
+
const cleanup = () => {
|
|
237
|
+
clearTimeout(timer);
|
|
238
|
+
socket.destroy();
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
socket.on("error", (err) => {
|
|
242
|
+
if (!settled) {
|
|
243
|
+
settled = true;
|
|
244
|
+
cleanup();
|
|
245
|
+
reject(err);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
socket.on("connect", () => {
|
|
250
|
+
const key = randomBytes(16).toString("base64");
|
|
251
|
+
const upgrade = [
|
|
252
|
+
"GET / HTTP/1.1",
|
|
253
|
+
`Host: ${DAEMON_HOST}:${DAEMON_PORT}`,
|
|
254
|
+
"Upgrade: websocket",
|
|
255
|
+
"Connection: Upgrade",
|
|
256
|
+
`Sec-WebSocket-Key: ${key}`,
|
|
257
|
+
"Sec-WebSocket-Version: 13",
|
|
258
|
+
"",
|
|
259
|
+
"",
|
|
260
|
+
].join("\r\n");
|
|
261
|
+
socket.write(upgrade);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
socket.on("data", (chunk) => {
|
|
265
|
+
buffer = Buffer.concat([buffer, chunk]);
|
|
266
|
+
|
|
267
|
+
if (!upgraded) {
|
|
268
|
+
const headerEnd = buffer.indexOf("\r\n\r\n");
|
|
269
|
+
if (headerEnd === -1) return;
|
|
270
|
+
const header = buffer.subarray(0, headerEnd).toString();
|
|
271
|
+
if (!header.includes("101")) {
|
|
272
|
+
settled = true;
|
|
273
|
+
cleanup();
|
|
274
|
+
reject(new Error("WebSocket upgrade failed"));
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
upgraded = true;
|
|
278
|
+
buffer = buffer.subarray(headerEnd + 4);
|
|
279
|
+
sendFrame(socket, request);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
while (true) {
|
|
283
|
+
const result = parseFrame(buffer);
|
|
284
|
+
if (!result) break;
|
|
285
|
+
buffer = result.rest;
|
|
286
|
+
|
|
287
|
+
try {
|
|
288
|
+
const parsed = JSON.parse(result.payload);
|
|
289
|
+
if (parsed.event || parsed.id !== id) continue;
|
|
290
|
+
if (!settled) {
|
|
291
|
+
settled = true;
|
|
292
|
+
cleanup();
|
|
293
|
+
if (parsed.error) {
|
|
294
|
+
reject(new Error(parsed.error));
|
|
295
|
+
} else {
|
|
296
|
+
resolve(parsed.result);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return;
|
|
300
|
+
} catch {
|
|
301
|
+
if (!settled) {
|
|
302
|
+
settled = true;
|
|
303
|
+
cleanup();
|
|
304
|
+
reject(new Error("Invalid JSON response from daemon"));
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function sendFrame(socket, text) {
|
|
314
|
+
const payload = Buffer.from(text, "utf8");
|
|
315
|
+
const mask = randomBytes(4);
|
|
316
|
+
const len = payload.length;
|
|
317
|
+
|
|
318
|
+
let header;
|
|
319
|
+
if (len < 126) {
|
|
320
|
+
header = Buffer.alloc(2);
|
|
321
|
+
header[0] = 0x81;
|
|
322
|
+
header[1] = 0x80 | len;
|
|
323
|
+
} else if (len < 65536) {
|
|
324
|
+
header = Buffer.alloc(4);
|
|
325
|
+
header[0] = 0x81;
|
|
326
|
+
header[1] = 0x80 | 126;
|
|
327
|
+
header.writeUInt16BE(len, 2);
|
|
328
|
+
} else {
|
|
329
|
+
header = Buffer.alloc(10);
|
|
330
|
+
header[0] = 0x81;
|
|
331
|
+
header[1] = 0x80 | 127;
|
|
332
|
+
header.writeBigUInt64BE(BigInt(len), 2);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const masked = Buffer.alloc(payload.length);
|
|
336
|
+
for (let i = 0; i < payload.length; i++) {
|
|
337
|
+
masked[i] = payload[i] ^ mask[i % 4];
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
socket.write(Buffer.concat([header, mask, masked]));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function parseFrame(buf) {
|
|
344
|
+
if (buf.length < 2) return null;
|
|
345
|
+
|
|
346
|
+
const isMasked = (buf[1] & 0x80) !== 0;
|
|
347
|
+
let payloadLen = buf[1] & 0x7f;
|
|
348
|
+
let offset = 2;
|
|
349
|
+
|
|
350
|
+
if (payloadLen === 126) {
|
|
351
|
+
if (buf.length < 4) return null;
|
|
352
|
+
payloadLen = buf.readUInt16BE(2);
|
|
353
|
+
offset = 4;
|
|
354
|
+
} else if (payloadLen === 127) {
|
|
355
|
+
if (buf.length < 10) return null;
|
|
356
|
+
payloadLen = Number(buf.readBigUInt64BE(2));
|
|
357
|
+
offset = 10;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
if (isMasked) offset += 4;
|
|
361
|
+
if (buf.length < offset + payloadLen) return null;
|
|
362
|
+
|
|
363
|
+
let payload = buf.subarray(offset, offset + payloadLen);
|
|
364
|
+
if (isMasked) {
|
|
365
|
+
const maskKey = buf.subarray(offset - 4, offset);
|
|
366
|
+
payload = Buffer.alloc(payloadLen);
|
|
367
|
+
for (let i = 0; i < payloadLen; i++) {
|
|
368
|
+
payload[i] = buf[offset + i] ^ maskKey[i % 4];
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
payload: payload.toString("utf8"),
|
|
374
|
+
rest: buf.subarray(offset + payloadLen),
|
|
375
|
+
};
|
|
376
|
+
}
|