screenhand 0.1.1 → 0.3.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 +193 -109
- package/bin/darwin-arm64/macos-bridge +0 -0
- package/dist/mcp-desktop.js +5876 -0
- package/dist/scripts/codex-monitor-daemon.js +335 -0
- package/dist/scripts/export-help-center.js +112 -0
- package/dist/scripts/marketing-loop.js +117 -0
- package/dist/scripts/observer-daemon.js +288 -0
- package/dist/scripts/orchestrator-daemon.js +399 -0
- package/dist/scripts/supervisor-daemon.js +272 -0
- package/dist/scripts/threads-campaign.js +208 -0
- package/dist/scripts/worker-daemon.js +228 -0
- package/dist/src/agent/cli.js +82 -0
- package/dist/src/agent/loop.js +274 -0
- package/dist/src/community/fetcher.js +109 -0
- package/dist/src/community/index.js +6 -0
- package/dist/src/community/publisher.js +191 -0
- package/dist/src/community/remote-api.js +121 -0
- package/dist/src/community/types.js +3 -0
- package/dist/src/community/validator.js +95 -0
- package/{src/config.ts → dist/src/config.js} +5 -10
- package/dist/src/context-tracker.js +489 -0
- package/{src/index.ts → dist/src/index.js} +32 -52
- package/dist/src/ingestion/coverage-auditor.js +233 -0
- package/dist/src/ingestion/doc-parser.js +164 -0
- package/dist/src/ingestion/index.js +8 -0
- package/dist/src/ingestion/menu-scanner.js +152 -0
- package/dist/src/ingestion/reference-merger.js +186 -0
- package/dist/src/ingestion/shortcut-extractor.js +180 -0
- package/dist/src/ingestion/tutorial-extractor.js +170 -0
- package/dist/src/ingestion/types.js +3 -0
- package/dist/src/jobs/manager.js +305 -0
- package/dist/src/jobs/runner.js +806 -0
- package/dist/src/jobs/store.js +102 -0
- package/dist/src/jobs/types.js +30 -0
- package/dist/src/jobs/worker.js +97 -0
- package/dist/src/learning/engine.js +356 -0
- package/dist/src/learning/index.js +9 -0
- package/dist/src/learning/locator-policy.js +120 -0
- package/dist/src/learning/pattern-policy.js +89 -0
- package/dist/src/learning/recovery-policy.js +116 -0
- package/dist/src/learning/sensor-policy.js +115 -0
- package/dist/src/learning/timing-model.js +204 -0
- package/dist/src/learning/topology-policy.js +90 -0
- package/dist/src/learning/types.js +9 -0
- package/dist/src/logging/timeline-logger.js +48 -0
- package/dist/src/mcp/mcp-stdio-server.js +464 -0
- package/dist/src/mcp/server.js +363 -0
- package/dist/src/mcp-entry.js +60 -0
- package/dist/src/memory/playbook-seeds.js +200 -0
- package/dist/src/memory/recall.js +222 -0
- package/dist/src/memory/research.js +104 -0
- package/dist/src/memory/seeds.js +101 -0
- package/dist/src/memory/service.js +446 -0
- package/dist/src/memory/session.js +169 -0
- package/dist/src/memory/store.js +451 -0
- package/{src/runtime/locator-cache.ts → dist/src/memory/types.js} +1 -17
- package/dist/src/monitor/codex-monitor.js +382 -0
- package/dist/src/monitor/task-queue.js +97 -0
- package/dist/src/monitor/types.js +62 -0
- package/dist/src/native/bridge-client.js +412 -0
- package/{src/native/macos-bridge-client.ts → dist/src/native/macos-bridge-client.js} +0 -1
- package/dist/src/observer/state.js +199 -0
- package/dist/src/observer/types.js +43 -0
- package/dist/src/orchestrator/state.js +68 -0
- package/dist/src/orchestrator/types.js +22 -0
- package/dist/src/perception/ax-source.js +162 -0
- package/dist/src/perception/cdp-source.js +162 -0
- package/dist/src/perception/coordinator.js +771 -0
- package/dist/src/perception/frame-differ.js +287 -0
- package/dist/src/perception/index.js +22 -0
- package/dist/src/perception/manager.js +199 -0
- package/dist/src/perception/types.js +47 -0
- package/dist/src/perception/vision-source.js +399 -0
- package/dist/src/planner/deterministic.js +298 -0
- package/dist/src/planner/executor.js +870 -0
- package/dist/src/planner/goal-store.js +92 -0
- package/dist/src/planner/index.js +21 -0
- package/dist/src/planner/planner.js +520 -0
- package/dist/src/planner/tool-registry.js +71 -0
- package/dist/src/planner/types.js +22 -0
- package/dist/src/platform/explorer.js +213 -0
- package/dist/src/platform/help-center-markdown.js +527 -0
- package/dist/src/platform/learner.js +257 -0
- package/dist/src/playbook/engine.js +486 -0
- package/dist/src/playbook/index.js +20 -0
- package/dist/src/playbook/mcp-recorder.js +204 -0
- package/dist/src/playbook/recorder.js +536 -0
- package/dist/src/playbook/runner.js +408 -0
- package/dist/src/playbook/store.js +312 -0
- package/dist/src/playbook/types.js +17 -0
- package/dist/src/recovery/detectors.js +156 -0
- package/dist/src/recovery/engine.js +327 -0
- package/dist/src/recovery/index.js +20 -0
- package/dist/src/recovery/strategies.js +274 -0
- package/dist/src/recovery/types.js +20 -0
- package/dist/src/runtime/accessibility-adapter.js +430 -0
- package/dist/src/runtime/app-adapter.js +64 -0
- package/dist/src/runtime/applescript-adapter.js +305 -0
- package/dist/src/runtime/ax-role-map.js +96 -0
- package/dist/src/runtime/browser-adapter.js +52 -0
- package/dist/src/runtime/cdp-chrome-adapter.js +521 -0
- package/dist/src/runtime/composite-adapter.js +221 -0
- package/dist/src/runtime/execution-contract.js +159 -0
- package/dist/src/runtime/executor.js +286 -0
- package/dist/src/runtime/locator-cache.js +50 -0
- package/dist/src/runtime/planning-loop.js +63 -0
- package/dist/src/runtime/service.js +432 -0
- package/dist/src/runtime/session-manager.js +63 -0
- package/dist/src/runtime/state-observer.js +121 -0
- package/dist/src/runtime/vision-adapter.js +225 -0
- package/dist/src/state/app-map-types.js +72 -0
- package/dist/src/state/app-map.js +1974 -0
- package/dist/src/state/entity-tracker.js +108 -0
- package/dist/src/state/fusion.js +96 -0
- package/dist/src/state/index.js +21 -0
- package/dist/src/state/ladder-generator.js +236 -0
- package/dist/src/state/persistence.js +156 -0
- package/dist/src/state/types.js +17 -0
- package/dist/src/state/world-model.js +1456 -0
- package/dist/src/supervisor/locks.js +186 -0
- package/dist/src/supervisor/supervisor.js +403 -0
- package/dist/src/supervisor/types.js +30 -0
- package/dist/src/test-mcp-protocol.js +154 -0
- package/dist/src/types.js +17 -0
- package/dist/src/util/atomic-write.js +133 -0
- package/dist/src/util/sanitize.js +146 -0
- package/dist-app-maps/com.figma.Desktop.json +959 -0
- package/dist-app-maps/com.hnc.Discord.json +1146 -0
- package/dist-app-maps/notion.id.json +2831 -0
- package/dist-playbooks/canva-screenhand-carousel.json +445 -0
- package/dist-playbooks/codex-desktop.json +76 -0
- package/dist-playbooks/competitor-research-stack.json +122 -0
- package/dist-playbooks/davinci-color-grade.json +153 -0
- package/dist-playbooks/davinci-edit-timeline.json +162 -0
- package/dist-playbooks/davinci-render.json +114 -0
- package/dist-playbooks/devto.json +52 -0
- package/dist-playbooks/discord.json +41 -0
- package/dist-playbooks/google-flow-create-project.json +59 -0
- package/dist-playbooks/google-flow-edit-image.json +90 -0
- package/dist-playbooks/google-flow-edit-video.json +90 -0
- package/dist-playbooks/google-flow-generate-image.json +68 -0
- package/dist-playbooks/google-flow-generate-video.json +191 -0
- package/dist-playbooks/google-flow-open-project.json +48 -0
- package/dist-playbooks/google-flow-open-scenebuilder.json +64 -0
- package/dist-playbooks/google-flow-search-assets.json +64 -0
- package/dist-playbooks/instagram.json +57 -0
- package/dist-playbooks/linkedin.json +52 -0
- package/dist-playbooks/n8n.json +43 -0
- package/dist-playbooks/reddit.json +52 -0
- package/dist-playbooks/threads.json +59 -0
- package/dist-playbooks/x-twitter.json +59 -0
- package/dist-playbooks/youtube.json +59 -0
- package/dist-references/canva.json +646 -0
- package/dist-references/codex-desktop.json +305 -0
- package/dist-references/davinci-resolve-keyboard.json +594 -0
- package/dist-references/davinci-resolve-menu-map.json +1139 -0
- package/dist-references/davinci-resolve-menus-batch1.json +116 -0
- package/dist-references/davinci-resolve-menus-batch2.json +372 -0
- package/dist-references/davinci-resolve-menus-batch3.json +330 -0
- package/dist-references/davinci-resolve-menus-batch4.json +297 -0
- package/dist-references/davinci-resolve-shortcuts.json +333 -0
- package/dist-references/devto.json +317 -0
- package/dist-references/discord.json +549 -0
- package/dist-references/figma.json +1186 -0
- package/dist-references/finder.json +146 -0
- package/dist-references/google-ads-transparency.json +95 -0
- package/dist-references/google-flow.json +649 -0
- package/dist-references/instagram.json +341 -0
- package/dist-references/linkedin.json +324 -0
- package/dist-references/meta-ad-library.json +86 -0
- package/dist-references/n8n.json +387 -0
- package/dist-references/notes.json +27 -0
- package/dist-references/notion.json +163 -0
- package/dist-references/reddit.json +341 -0
- package/dist-references/threads.json +337 -0
- package/dist-references/x-twitter.json +403 -0
- package/dist-references/youtube.json +373 -0
- package/native/macos-bridge/Package.swift +1 -0
- package/native/macos-bridge/Sources/AccessibilityBridge.swift +257 -36
- package/native/macos-bridge/Sources/AppManagement.swift +212 -2
- package/native/macos-bridge/Sources/CoreGraphicsBridge.swift +348 -53
- package/native/macos-bridge/Sources/StreamCapture.swift +136 -0
- package/native/macos-bridge/Sources/VisionBridge.swift +165 -7
- package/native/macos-bridge/Sources/main.swift +169 -16
- package/native/windows-bridge/Program.cs +5 -0
- package/native/windows-bridge/ScreenCapture.cs +124 -0
- package/package.json +29 -4
- package/scripts/postinstall.cjs +127 -0
- package/.claude/commands/automate.md +0 -28
- package/.claude/commands/debug-ui.md +0 -19
- package/.claude/commands/screenshot.md +0 -15
- package/.github/FUNDING.yml +0 -1
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -27
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -20
- package/.mcp.json +0 -8
- package/DESKTOP_MCP_GUIDE.md +0 -92
- package/SECURITY.md +0 -44
- package/docs/architecture.md +0 -47
- package/install-skills.sh +0 -19
- package/mcp-bridge.ts +0 -271
- package/mcp-desktop.ts +0 -1221
- package/playbooks/instagram.json +0 -41
- package/playbooks/instagram_v2.json +0 -201
- package/playbooks/x_v1.json +0 -211
- package/scripts/devpost-live-loop.mjs +0 -421
- package/src/logging/timeline-logger.ts +0 -55
- package/src/mcp/server.ts +0 -449
- package/src/memory/recall.ts +0 -191
- package/src/memory/research.ts +0 -146
- package/src/memory/seeds.ts +0 -123
- package/src/memory/session.ts +0 -201
- package/src/memory/store.ts +0 -434
- package/src/memory/types.ts +0 -69
- package/src/native/bridge-client.ts +0 -239
- package/src/runtime/accessibility-adapter.ts +0 -487
- package/src/runtime/app-adapter.ts +0 -169
- package/src/runtime/applescript-adapter.ts +0 -376
- package/src/runtime/ax-role-map.ts +0 -102
- package/src/runtime/browser-adapter.ts +0 -129
- package/src/runtime/cdp-chrome-adapter.ts +0 -676
- package/src/runtime/composite-adapter.ts +0 -274
- package/src/runtime/executor.ts +0 -396
- package/src/runtime/planning-loop.ts +0 -81
- package/src/runtime/service.ts +0 -448
- package/src/runtime/session-manager.ts +0 -50
- package/src/runtime/state-observer.ts +0 -136
- package/src/runtime/vision-adapter.ts +0 -297
- package/src/types.ts +0 -297
- package/tests/bridge-client.test.ts +0 -176
- package/tests/browser-stealth.test.ts +0 -210
- package/tests/composite-adapter.test.ts +0 -64
- package/tests/mcp-server.test.ts +0 -151
- package/tests/memory-recall.test.ts +0 -339
- package/tests/memory-research.test.ts +0 -159
- package/tests/memory-seeds.test.ts +0 -120
- package/tests/memory-store.test.ts +0 -392
- package/tests/types.test.ts +0 -92
- package/tsconfig.check.json +0 -17
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -8
- /package/{playbooks → dist-references}/devpost.json +0 -0
package/src/types.ts
DELETED
|
@@ -1,297 +0,0 @@
|
|
|
1
|
-
// Copyright (C) 2025 Clazro Technology Private Limited
|
|
2
|
-
// SPDX-License-Identifier: AGPL-3.0-only
|
|
3
|
-
//
|
|
4
|
-
// This file is part of ScreenHand.
|
|
5
|
-
//
|
|
6
|
-
// ScreenHand is free software: you can redistribute it and/or modify
|
|
7
|
-
// it under the terms of the GNU Affero General Public License as
|
|
8
|
-
// published by the Free Software Foundation, version 3.
|
|
9
|
-
//
|
|
10
|
-
// ScreenHand is distributed in the hope that it will be useful,
|
|
11
|
-
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
12
|
-
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
13
|
-
// GNU Affero General Public License for more details.
|
|
14
|
-
//
|
|
15
|
-
// You should have received a copy of the GNU Affero General Public License
|
|
16
|
-
// along with ScreenHand. If not, see <https://www.gnu.org/licenses/>.
|
|
17
|
-
|
|
18
|
-
export type ToolName =
|
|
19
|
-
| "session_start"
|
|
20
|
-
| "navigate"
|
|
21
|
-
| "press"
|
|
22
|
-
| "type_into"
|
|
23
|
-
| "wait_for"
|
|
24
|
-
| "extract"
|
|
25
|
-
| "screenshot"
|
|
26
|
-
| "app_launch"
|
|
27
|
-
| "app_focus"
|
|
28
|
-
| "app_list"
|
|
29
|
-
| "window_list"
|
|
30
|
-
| "menu_click"
|
|
31
|
-
| "key_combo"
|
|
32
|
-
| "element_tree"
|
|
33
|
-
| "observe_start"
|
|
34
|
-
| "observe_stop"
|
|
35
|
-
| "drag"
|
|
36
|
-
| "scroll";
|
|
37
|
-
|
|
38
|
-
export type Target =
|
|
39
|
-
| { type: "selector"; value: string }
|
|
40
|
-
| { type: "text"; value: string; exact?: boolean }
|
|
41
|
-
| { type: "role"; role: string; name: string; exact?: boolean }
|
|
42
|
-
| { type: "ax_path"; path: string[] }
|
|
43
|
-
| { type: "ax_attribute"; attribute: string; value: string }
|
|
44
|
-
| { type: "coordinates"; x: number; y: number }
|
|
45
|
-
| { type: "image"; base64: string; confidence?: number };
|
|
46
|
-
|
|
47
|
-
export type WaitCondition =
|
|
48
|
-
| { type: "selector_visible"; selector: string }
|
|
49
|
-
| { type: "selector_hidden"; selector: string }
|
|
50
|
-
| { type: "url_matches"; regex: string }
|
|
51
|
-
| { type: "text_appears"; text: string }
|
|
52
|
-
| { type: "spinner_disappears"; selector: string }
|
|
53
|
-
| { type: "element_exists"; target: Target }
|
|
54
|
-
| { type: "element_gone"; target: Target }
|
|
55
|
-
| { type: "window_title_matches"; regex: string }
|
|
56
|
-
| { type: "app_idle"; bundleId: string; timeoutMs?: number };
|
|
57
|
-
|
|
58
|
-
export type ExtractFormat = "text" | "table" | "json";
|
|
59
|
-
|
|
60
|
-
export type ActionStatus = "success" | "failed";
|
|
61
|
-
|
|
62
|
-
export interface ActionBudget {
|
|
63
|
-
locateMs: number;
|
|
64
|
-
actMs: number;
|
|
65
|
-
verifyMs: number;
|
|
66
|
-
maxRetries: number;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export interface SessionInfo {
|
|
70
|
-
sessionId: string;
|
|
71
|
-
profile: string;
|
|
72
|
-
createdAt: string;
|
|
73
|
-
adapterType?: "cdp" | "accessibility" | "applescript" | "vision" | "composite";
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface PageMeta {
|
|
77
|
-
url: string;
|
|
78
|
-
title: string;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export interface AppContext {
|
|
82
|
-
bundleId: string;
|
|
83
|
-
appName: string;
|
|
84
|
-
pid: number;
|
|
85
|
-
windowTitle: string;
|
|
86
|
-
windowId?: number;
|
|
87
|
-
url?: string;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export interface LocatedElement {
|
|
91
|
-
handleId: string;
|
|
92
|
-
locatorUsed: string;
|
|
93
|
-
role?: string;
|
|
94
|
-
label?: string;
|
|
95
|
-
coordinates?: { x: number; y: number; width: number; height: number };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface LocatorAttempt {
|
|
99
|
-
strategy: string;
|
|
100
|
-
target: string;
|
|
101
|
-
timeoutMs: number;
|
|
102
|
-
matched: boolean;
|
|
103
|
-
reason?: string;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export interface RuntimeError {
|
|
107
|
-
code:
|
|
108
|
-
| "SESSION_NOT_FOUND"
|
|
109
|
-
| "LOCATE_FAILED"
|
|
110
|
-
| "ACTION_FAILED"
|
|
111
|
-
| "VERIFY_FAILED"
|
|
112
|
-
| "TIMEOUT"
|
|
113
|
-
| "NOT_IMPLEMENTED"
|
|
114
|
-
| "PERMISSION_DENIED"
|
|
115
|
-
| "APP_NOT_FOUND"
|
|
116
|
-
| "BRIDGE_ERROR";
|
|
117
|
-
message: string;
|
|
118
|
-
page?: PageMeta;
|
|
119
|
-
appContext?: AppContext;
|
|
120
|
-
attempts?: LocatorAttempt[];
|
|
121
|
-
cause?: string;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
export interface ActionTelemetry {
|
|
125
|
-
action: string;
|
|
126
|
-
sessionId: string;
|
|
127
|
-
startedAt: string;
|
|
128
|
-
finishedAt?: string;
|
|
129
|
-
totalMs?: number;
|
|
130
|
-
locateMs: number;
|
|
131
|
-
actMs: number;
|
|
132
|
-
verifyMs: number;
|
|
133
|
-
retries: number;
|
|
134
|
-
status?: ActionStatus;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export interface ToolSuccess<T> {
|
|
138
|
-
ok: true;
|
|
139
|
-
data: T;
|
|
140
|
-
telemetry: ActionTelemetry;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
export interface ToolFailure {
|
|
144
|
-
ok: false;
|
|
145
|
-
error: RuntimeError;
|
|
146
|
-
telemetry: ActionTelemetry;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
export type ToolResult<T> = ToolSuccess<T> | ToolFailure;
|
|
150
|
-
|
|
151
|
-
export interface PressInput {
|
|
152
|
-
sessionId: string;
|
|
153
|
-
target: Target;
|
|
154
|
-
verify?: WaitCondition;
|
|
155
|
-
budget?: Partial<ActionBudget>;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
export interface TypeIntoInput {
|
|
159
|
-
sessionId: string;
|
|
160
|
-
target: Target;
|
|
161
|
-
text: string;
|
|
162
|
-
clear?: boolean;
|
|
163
|
-
verifyValue?: boolean;
|
|
164
|
-
verify?: WaitCondition;
|
|
165
|
-
budget?: Partial<ActionBudget>;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export interface NavigateInput {
|
|
169
|
-
sessionId: string;
|
|
170
|
-
url: string;
|
|
171
|
-
timeoutMs?: number;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
export interface WaitForInput {
|
|
175
|
-
sessionId: string;
|
|
176
|
-
condition: WaitCondition;
|
|
177
|
-
timeoutMs?: number;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
export interface ExtractInput {
|
|
181
|
-
sessionId: string;
|
|
182
|
-
target: Target;
|
|
183
|
-
format: ExtractFormat;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
export interface ScreenshotInput {
|
|
187
|
-
sessionId: string;
|
|
188
|
-
region?: {
|
|
189
|
-
x: number;
|
|
190
|
-
y: number;
|
|
191
|
-
width: number;
|
|
192
|
-
height: number;
|
|
193
|
-
};
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
export interface AppLaunchInput {
|
|
197
|
-
sessionId: string;
|
|
198
|
-
bundleId: string;
|
|
199
|
-
waitForReady?: boolean;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
export interface AppFocusInput {
|
|
203
|
-
sessionId: string;
|
|
204
|
-
bundleId: string;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
export interface MenuClickInput {
|
|
208
|
-
sessionId: string;
|
|
209
|
-
menuPath: string[];
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export interface KeyComboInput {
|
|
213
|
-
sessionId: string;
|
|
214
|
-
keys: string[];
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
export interface ElementTreeInput {
|
|
218
|
-
sessionId: string;
|
|
219
|
-
maxDepth?: number;
|
|
220
|
-
root?: Target;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export interface DragInput {
|
|
224
|
-
sessionId: string;
|
|
225
|
-
from: Target;
|
|
226
|
-
to: Target;
|
|
227
|
-
budget?: Partial<ActionBudget>;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
export interface ScrollInput {
|
|
231
|
-
sessionId: string;
|
|
232
|
-
target?: Target;
|
|
233
|
-
direction: "up" | "down" | "left" | "right";
|
|
234
|
-
amount?: number;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export interface ObserveStartInput {
|
|
238
|
-
sessionId: string;
|
|
239
|
-
events?: UIEventType[];
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
export interface ObserveStopInput {
|
|
243
|
-
sessionId: string;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export type UIEventType =
|
|
247
|
-
| "value_changed"
|
|
248
|
-
| "focus_changed"
|
|
249
|
-
| "window_created"
|
|
250
|
-
| "window_closed"
|
|
251
|
-
| "dialog_appeared"
|
|
252
|
-
| "menu_opened"
|
|
253
|
-
| "title_changed"
|
|
254
|
-
| "layout_changed"
|
|
255
|
-
| "app_activated"
|
|
256
|
-
| "app_deactivated";
|
|
257
|
-
|
|
258
|
-
export interface UIEvent {
|
|
259
|
-
type: UIEventType;
|
|
260
|
-
timestamp: string;
|
|
261
|
-
pid: number;
|
|
262
|
-
bundleId?: string;
|
|
263
|
-
elementRole?: string;
|
|
264
|
-
elementLabel?: string;
|
|
265
|
-
oldValue?: string;
|
|
266
|
-
newValue?: string;
|
|
267
|
-
windowTitle?: string;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
export interface RunningApp {
|
|
271
|
-
bundleId: string;
|
|
272
|
-
name: string;
|
|
273
|
-
pid: number;
|
|
274
|
-
isActive: boolean;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
export interface WindowInfo {
|
|
278
|
-
windowId: number;
|
|
279
|
-
title: string;
|
|
280
|
-
bundleId: string;
|
|
281
|
-
pid: number;
|
|
282
|
-
bounds: { x: number; y: number; width: number; height: number };
|
|
283
|
-
isOnScreen: boolean;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export interface AXNode {
|
|
287
|
-
role: string;
|
|
288
|
-
title?: string;
|
|
289
|
-
value?: string;
|
|
290
|
-
description?: string;
|
|
291
|
-
identifier?: string;
|
|
292
|
-
enabled?: boolean;
|
|
293
|
-
focused?: boolean;
|
|
294
|
-
position?: { x: number; y: number };
|
|
295
|
-
size?: { width: number; height: number };
|
|
296
|
-
children?: AXNode[];
|
|
297
|
-
}
|
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { BridgeClient } from "../src/native/bridge-client.js";
|
|
3
|
-
import { EventEmitter } from "node:events";
|
|
4
|
-
import { ChildProcess } from "node:child_process";
|
|
5
|
-
import path from "node:path";
|
|
6
|
-
|
|
7
|
-
// ── Platform detection ──
|
|
8
|
-
|
|
9
|
-
describe("BridgeClient platform detection", () => {
|
|
10
|
-
it("selects macOS binary path when platform is darwin", () => {
|
|
11
|
-
const originalPlatform = process.platform;
|
|
12
|
-
Object.defineProperty(process, "platform", { value: "darwin", writable: true });
|
|
13
|
-
|
|
14
|
-
const client = new BridgeClient();
|
|
15
|
-
// Access the private binaryPath via any cast
|
|
16
|
-
const binaryPath = (client as any).binaryPath as string;
|
|
17
|
-
expect(binaryPath).toContain("macos-bridge");
|
|
18
|
-
expect(binaryPath).not.toContain("windows-bridge");
|
|
19
|
-
|
|
20
|
-
Object.defineProperty(process, "platform", { value: originalPlatform, writable: true });
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("selects Windows binary path when platform is win32", () => {
|
|
24
|
-
const originalPlatform = process.platform;
|
|
25
|
-
Object.defineProperty(process, "platform", { value: "win32", writable: true });
|
|
26
|
-
|
|
27
|
-
const client = new BridgeClient();
|
|
28
|
-
const binaryPath = (client as any).binaryPath as string;
|
|
29
|
-
expect(binaryPath).toContain("windows-bridge");
|
|
30
|
-
expect(binaryPath).toContain(".exe");
|
|
31
|
-
|
|
32
|
-
Object.defineProperty(process, "platform", { value: originalPlatform, writable: true });
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("accepts a custom binary path", () => {
|
|
36
|
-
const customPath = "/custom/path/to/bridge";
|
|
37
|
-
const client = new BridgeClient(customPath);
|
|
38
|
-
expect((client as any).binaryPath).toBe(customPath);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
// ── JSON-RPC line parsing ──
|
|
43
|
-
|
|
44
|
-
describe("BridgeClient JSON-RPC parsing", () => {
|
|
45
|
-
let client: BridgeClient;
|
|
46
|
-
|
|
47
|
-
beforeEach(() => {
|
|
48
|
-
client = new BridgeClient("/fake/path");
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
afterEach(async () => {
|
|
52
|
-
await client.stop();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("resolves pending request on valid response", async () => {
|
|
56
|
-
// Manually set up a pending request and feed it a line
|
|
57
|
-
const pending = (client as any).pending as Map<number, any>;
|
|
58
|
-
|
|
59
|
-
const resultPromise = new Promise((resolve, reject) => {
|
|
60
|
-
pending.set(1, {
|
|
61
|
-
resolve,
|
|
62
|
-
reject,
|
|
63
|
-
timer: setTimeout(() => reject(new Error("timeout")), 5000),
|
|
64
|
-
});
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
// Simulate receiving a response line
|
|
68
|
-
(client as any).handleLine(JSON.stringify({ id: 1, result: { pong: true } }));
|
|
69
|
-
|
|
70
|
-
const result = await resultPromise;
|
|
71
|
-
expect(result).toEqual({ pong: true });
|
|
72
|
-
expect(pending.size).toBe(0);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
it("rejects pending request on error response", async () => {
|
|
76
|
-
const pending = (client as any).pending as Map<number, any>;
|
|
77
|
-
|
|
78
|
-
const resultPromise = new Promise((resolve, reject) => {
|
|
79
|
-
pending.set(2, {
|
|
80
|
-
resolve,
|
|
81
|
-
reject,
|
|
82
|
-
timer: setTimeout(() => reject(new Error("timeout")), 5000),
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
(client as any).handleLine(JSON.stringify({
|
|
87
|
-
id: 2,
|
|
88
|
-
result: null,
|
|
89
|
-
error: { code: -1, message: "Not found: element" },
|
|
90
|
-
}));
|
|
91
|
-
|
|
92
|
-
await expect(resultPromise).rejects.toThrow("Not found: element");
|
|
93
|
-
expect(pending.size).toBe(0);
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
it("emits ax-event for event messages", async () => {
|
|
97
|
-
const eventPromise = new Promise<Record<string, unknown>>((resolve) => {
|
|
98
|
-
client.on("ax-event", resolve);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
(client as any).handleLine(JSON.stringify({
|
|
102
|
-
id: 0,
|
|
103
|
-
event: { type: "value_changed", pid: 123 },
|
|
104
|
-
}));
|
|
105
|
-
|
|
106
|
-
const event = await eventPromise;
|
|
107
|
-
expect(event).toEqual({ type: "value_changed", pid: 123 });
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it("ignores malformed JSON lines", () => {
|
|
111
|
-
// Should not throw
|
|
112
|
-
(client as any).handleLine("not json at all");
|
|
113
|
-
(client as any).handleLine("{incomplete");
|
|
114
|
-
(client as any).handleLine("");
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
it("ignores responses for unknown request IDs", () => {
|
|
118
|
-
// Should not throw
|
|
119
|
-
(client as any).handleLine(JSON.stringify({ id: 999, result: "orphan" }));
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
// ── Lifecycle ──
|
|
124
|
-
|
|
125
|
-
describe("BridgeClient lifecycle", () => {
|
|
126
|
-
it("rejects all pending requests on stop", async () => {
|
|
127
|
-
const client = new BridgeClient("/fake/path");
|
|
128
|
-
const pending = (client as any).pending as Map<number, any>;
|
|
129
|
-
|
|
130
|
-
const p1 = new Promise((resolve, reject) => {
|
|
131
|
-
pending.set(10, {
|
|
132
|
-
resolve,
|
|
133
|
-
reject,
|
|
134
|
-
timer: setTimeout(() => reject(new Error("timeout")), 5000),
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
const p2 = new Promise((resolve, reject) => {
|
|
139
|
-
pending.set(11, {
|
|
140
|
-
resolve,
|
|
141
|
-
reject,
|
|
142
|
-
timer: setTimeout(() => reject(new Error("timeout")), 5000),
|
|
143
|
-
});
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
await client.stop();
|
|
147
|
-
|
|
148
|
-
await expect(p1).rejects.toThrow("Bridge stopped");
|
|
149
|
-
await expect(p2).rejects.toThrow("Bridge stopped");
|
|
150
|
-
expect(pending.size).toBe(0);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("start is idempotent after first call", async () => {
|
|
154
|
-
const client = new BridgeClient("/fake/path");
|
|
155
|
-
(client as any).started = true;
|
|
156
|
-
|
|
157
|
-
// Second start() should return immediately without spawning
|
|
158
|
-
await client.start(); // should not throw or spawn
|
|
159
|
-
expect((client as any).process).toBeNull();
|
|
160
|
-
});
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
// ── MacOSBridgeClient backward compat ──
|
|
164
|
-
|
|
165
|
-
describe("MacOSBridgeClient backward compatibility", () => {
|
|
166
|
-
it("MacOSBridgeClient is the same as BridgeClient", async () => {
|
|
167
|
-
const { MacOSBridgeClient } = await import("../src/native/bridge-client.js");
|
|
168
|
-
expect(MacOSBridgeClient).toBe(BridgeClient);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("re-export from macos-bridge-client.ts works", async () => {
|
|
172
|
-
const mod = await import("../src/native/macos-bridge-client.js");
|
|
173
|
-
expect(mod.MacOSBridgeClient).toBe(BridgeClient);
|
|
174
|
-
expect(mod.BridgeClient).toBe(BridgeClient);
|
|
175
|
-
});
|
|
176
|
-
});
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Tests for browser stealth / anti-detection features.
|
|
5
|
-
* These test the stealth script validity, CDP Input event sequences,
|
|
6
|
-
* and human-like timing without requiring a real Chrome instance.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
// ── Stealth script validation ──
|
|
10
|
-
|
|
11
|
-
// Extract the stealth script source inline (mirrors mcp-desktop.ts STEALTH_SCRIPT)
|
|
12
|
-
const STEALTH_SCRIPT = `
|
|
13
|
-
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
14
|
-
for (const key of Object.keys(window)) {
|
|
15
|
-
if (key.match(/^cdc_/)) delete (window)[key];
|
|
16
|
-
}
|
|
17
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
18
|
-
get: () => [
|
|
19
|
-
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
20
|
-
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
21
|
-
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
|
|
22
|
-
],
|
|
23
|
-
});
|
|
24
|
-
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
|
25
|
-
`;
|
|
26
|
-
|
|
27
|
-
describe("browser_stealth", () => {
|
|
28
|
-
it("stealth patches are valid JavaScript (no syntax errors)", () => {
|
|
29
|
-
// If this throws, the script has syntax errors
|
|
30
|
-
expect(() => new Function(STEALTH_SCRIPT)).not.toThrow();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
it("stealth script hides webdriver property", () => {
|
|
34
|
-
// Simulate in a minimal environment
|
|
35
|
-
const nav: any = { webdriver: true };
|
|
36
|
-
const fn = new Function("navigator", `
|
|
37
|
-
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
38
|
-
`);
|
|
39
|
-
fn(nav);
|
|
40
|
-
expect(nav.webdriver).toBeUndefined();
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it("stealth script sets realistic plugins", () => {
|
|
44
|
-
const nav: any = { plugins: [] };
|
|
45
|
-
const fn = new Function("navigator", `
|
|
46
|
-
Object.defineProperty(navigator, 'plugins', {
|
|
47
|
-
get: () => [
|
|
48
|
-
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
49
|
-
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
50
|
-
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
|
|
51
|
-
],
|
|
52
|
-
});
|
|
53
|
-
`);
|
|
54
|
-
fn(nav);
|
|
55
|
-
expect(nav.plugins).toHaveLength(3);
|
|
56
|
-
expect(nav.plugins[0].name).toBe("Chrome PDF Plugin");
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it("stealth script sets realistic languages", () => {
|
|
60
|
-
const nav: any = { languages: [] };
|
|
61
|
-
const fn = new Function("navigator", `
|
|
62
|
-
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
|
63
|
-
`);
|
|
64
|
-
fn(nav);
|
|
65
|
-
expect(nav.languages).toEqual(["en-US", "en"]);
|
|
66
|
-
});
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
// ── Mock CDP client for Input domain tests ──
|
|
70
|
-
|
|
71
|
-
function createMockCDPClient() {
|
|
72
|
-
const calls: { method: string; params: any }[] = [];
|
|
73
|
-
return {
|
|
74
|
-
calls,
|
|
75
|
-
Runtime: {
|
|
76
|
-
enable: vi.fn(),
|
|
77
|
-
evaluate: vi.fn().mockResolvedValue({
|
|
78
|
-
result: { value: { ok: true, x: 100, y: 200, text: "Submit" } },
|
|
79
|
-
}),
|
|
80
|
-
},
|
|
81
|
-
Input: {
|
|
82
|
-
dispatchKeyEvent: vi.fn().mockImplementation(async (params: any) => {
|
|
83
|
-
calls.push({ method: "Input.dispatchKeyEvent", params });
|
|
84
|
-
}),
|
|
85
|
-
dispatchMouseEvent: vi.fn().mockImplementation(async (params: any) => {
|
|
86
|
-
calls.push({ method: "Input.dispatchMouseEvent", params });
|
|
87
|
-
}),
|
|
88
|
-
},
|
|
89
|
-
Page: {
|
|
90
|
-
enable: vi.fn(),
|
|
91
|
-
addScriptToEvaluateOnNewDocument: vi.fn(),
|
|
92
|
-
},
|
|
93
|
-
close: vi.fn(),
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
describe("browser_fill_form (mock CDP)", () => {
|
|
98
|
-
it("types character by character using Input.dispatchKeyEvent", async () => {
|
|
99
|
-
const client = createMockCDPClient();
|
|
100
|
-
const text = "hello";
|
|
101
|
-
|
|
102
|
-
// Simulate what browser_fill_form does internally
|
|
103
|
-
// Clear: select all + backspace
|
|
104
|
-
await client.Input.dispatchKeyEvent({ type: "keyDown", key: "a", code: "KeyA", modifiers: 4 });
|
|
105
|
-
await client.Input.dispatchKeyEvent({ type: "keyUp", key: "a", code: "KeyA", modifiers: 4 });
|
|
106
|
-
await client.Input.dispatchKeyEvent({ type: "keyDown", key: "Backspace", code: "Backspace" });
|
|
107
|
-
await client.Input.dispatchKeyEvent({ type: "keyUp", key: "Backspace", code: "Backspace" });
|
|
108
|
-
|
|
109
|
-
// Type each char
|
|
110
|
-
for (const char of text) {
|
|
111
|
-
await client.Input.dispatchKeyEvent({ type: "keyDown", text: char, key: char, unmodifiedText: char });
|
|
112
|
-
await client.Input.dispatchKeyEvent({ type: "keyUp", text: char, key: char, unmodifiedText: char });
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 4 clear events + 2 per char = 4 + 10 = 14
|
|
116
|
-
const keyEvents = client.calls.filter(c => c.method === "Input.dispatchKeyEvent");
|
|
117
|
-
expect(keyEvents).toHaveLength(14);
|
|
118
|
-
|
|
119
|
-
// Verify keyDown/keyUp pairs for typed chars
|
|
120
|
-
const typedEvents = keyEvents.slice(4); // skip clear events
|
|
121
|
-
for (let i = 0; i < text.length; i++) {
|
|
122
|
-
const down = typedEvents[i * 2];
|
|
123
|
-
const up = typedEvents[i * 2 + 1];
|
|
124
|
-
expect(down.params.type).toBe("keyDown");
|
|
125
|
-
expect(down.params.text).toBe(text[i]);
|
|
126
|
-
expect(up.params.type).toBe("keyUp");
|
|
127
|
-
expect(up.params.text).toBe(text[i]);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
it("does not use el.value assignment (no Runtime.evaluate with el.value =)", () => {
|
|
132
|
-
// The implementation should use Input.dispatchKeyEvent, NOT el.value = text
|
|
133
|
-
// This test verifies the approach by checking that the mock client
|
|
134
|
-
// receives dispatchKeyEvent calls instead of evaluate calls with el.value
|
|
135
|
-
const client = createMockCDPClient();
|
|
136
|
-
|
|
137
|
-
// The evaluate call should only be for focusing, not for setting value
|
|
138
|
-
// In the real implementation, evaluate is called once for focus only
|
|
139
|
-
expect(client.Runtime.evaluate).not.toHaveBeenCalledWith(
|
|
140
|
-
expect.objectContaining({
|
|
141
|
-
expression: expect.stringContaining("el.value ="),
|
|
142
|
-
})
|
|
143
|
-
);
|
|
144
|
-
});
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
describe("browser_human_click (mock CDP)", () => {
|
|
148
|
-
it("dispatches mouseMoved → mousePressed → mouseReleased sequence", async () => {
|
|
149
|
-
const client = createMockCDPClient();
|
|
150
|
-
const x = 100;
|
|
151
|
-
const y = 200;
|
|
152
|
-
|
|
153
|
-
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x, y });
|
|
154
|
-
await client.Input.dispatchMouseEvent({ type: "mousePressed", x, y, button: "left", clickCount: 1 });
|
|
155
|
-
await client.Input.dispatchMouseEvent({ type: "mouseReleased", x, y, button: "left", clickCount: 1 });
|
|
156
|
-
|
|
157
|
-
const mouseEvents = client.calls.filter(c => c.method === "Input.dispatchMouseEvent");
|
|
158
|
-
expect(mouseEvents).toHaveLength(3);
|
|
159
|
-
expect(mouseEvents[0].params.type).toBe("mouseMoved");
|
|
160
|
-
expect(mouseEvents[1].params.type).toBe("mousePressed");
|
|
161
|
-
expect(mouseEvents[1].params.button).toBe("left");
|
|
162
|
-
expect(mouseEvents[2].params.type).toBe("mouseReleased");
|
|
163
|
-
expect(mouseEvents[2].params.button).toBe("left");
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
it("uses correct coordinates from element bounding rect", async () => {
|
|
167
|
-
const client = createMockCDPClient();
|
|
168
|
-
// Simulate element at (50, 100) with size 200x40
|
|
169
|
-
const rect = { x: 50, y: 100, width: 200, height: 40 };
|
|
170
|
-
const centerX = rect.x + rect.width / 2; // 150
|
|
171
|
-
const centerY = rect.y + rect.height / 2; // 120
|
|
172
|
-
|
|
173
|
-
await client.Input.dispatchMouseEvent({ type: "mouseMoved", x: centerX, y: centerY });
|
|
174
|
-
await client.Input.dispatchMouseEvent({ type: "mousePressed", x: centerX, y: centerY, button: "left", clickCount: 1 });
|
|
175
|
-
await client.Input.dispatchMouseEvent({ type: "mouseReleased", x: centerX, y: centerY, button: "left", clickCount: 1 });
|
|
176
|
-
|
|
177
|
-
const mouseEvents = client.calls.filter(c => c.method === "Input.dispatchMouseEvent");
|
|
178
|
-
for (const evt of mouseEvents) {
|
|
179
|
-
expect(evt.params.x).toBe(150);
|
|
180
|
-
expect(evt.params.y).toBe(120);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
describe("random delay timing", () => {
|
|
186
|
-
it("randomDelay produces values within expected range", async () => {
|
|
187
|
-
// Test the delay logic directly
|
|
188
|
-
const min = 30;
|
|
189
|
-
const max = 80;
|
|
190
|
-
const samples: number[] = [];
|
|
191
|
-
|
|
192
|
-
for (let i = 0; i < 50; i++) {
|
|
193
|
-
const start = Date.now();
|
|
194
|
-
await new Promise(r => setTimeout(r, min + Math.random() * (max - min)));
|
|
195
|
-
const elapsed = Date.now() - start;
|
|
196
|
-
samples.push(elapsed);
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// All delays should be roughly in the expected range
|
|
200
|
-
// Allow some tolerance for timer imprecision
|
|
201
|
-
for (const s of samples) {
|
|
202
|
-
expect(s).toBeGreaterThanOrEqual(min - 5);
|
|
203
|
-
expect(s).toBeLessThan(max + 50); // generous upper bound for CI
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Should not all be the same (randomness check)
|
|
207
|
-
const unique = new Set(samples);
|
|
208
|
-
expect(unique.size).toBeGreaterThan(1);
|
|
209
|
-
});
|
|
210
|
-
});
|