opencode-pair-autonomy 1.0.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 +90 -0
- package/bin/opencode-pair-autonomy.js +20 -0
- package/dist/__tests__/comment-guard.test.d.ts +1 -0
- package/dist/__tests__/config.test.d.ts +1 -0
- package/dist/__tests__/learning.test.d.ts +1 -0
- package/dist/__tests__/plan-mode.test.d.ts +1 -0
- package/dist/agents.d.ts +2 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +15351 -0
- package/dist/commands.d.ts +2 -0
- package/dist/config.d.ts +3 -0
- package/dist/hooks/comment-guard.d.ts +15 -0
- package/dist/hooks/file-edited.d.ts +7 -0
- package/dist/hooks/index.d.ts +46 -0
- package/dist/hooks/post-tool-use.d.ts +5 -0
- package/dist/hooks/pre-compact.d.ts +4 -0
- package/dist/hooks/pre-tool-use.d.ts +5 -0
- package/dist/hooks/prompt-refiner.d.ts +38 -0
- package/dist/hooks/runtime.d.ts +91 -0
- package/dist/hooks/sdk.d.ts +6 -0
- package/dist/hooks/session-end.d.ts +4 -0
- package/dist/hooks/session-start.d.ts +19 -0
- package/dist/hooks/stop.d.ts +5 -0
- package/dist/i18n/index.d.ts +15 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +17823 -0
- package/dist/installer.d.ts +12 -0
- package/dist/learning/analyzer.d.ts +15 -0
- package/dist/learning/store.d.ts +4 -0
- package/dist/learning/types.d.ts +32 -0
- package/dist/mcp.d.ts +4 -0
- package/dist/project-facts.d.ts +8 -0
- package/dist/prompts/coordinator.d.ts +2 -0
- package/dist/prompts/shared.d.ts +5 -0
- package/dist/prompts/workers.d.ts +8 -0
- package/dist/types.d.ts +81 -0
- package/dist/utils.d.ts +6 -0
- package/examples/opencode-pair-autonomy.jsonc +35 -0
- package/examples/opencode.jsonc +17 -0
- package/package.json +103 -0
- package/vendor/mcp/pg-mcp/README.md +91 -0
- package/vendor/mcp/pg-mcp/config.example.json +26 -0
- package/vendor/mcp/pg-mcp/config.json +15 -0
- package/vendor/mcp/pg-mcp/package-lock.json +1288 -0
- package/vendor/mcp/pg-mcp/package.json +18 -0
- package/vendor/mcp/pg-mcp/src/config.js +71 -0
- package/vendor/mcp/pg-mcp/src/db.js +85 -0
- package/vendor/mcp/pg-mcp/src/index.js +203 -0
- package/vendor/mcp/pg-mcp/src/sqlGuard.js +75 -0
- package/vendor/mcp/pg-mcp/src/tools.js +89 -0
- package/vendor/mcp/ssh-mcp/README.md +46 -0
- package/vendor/mcp/ssh-mcp/config.example.json +23 -0
- package/vendor/mcp/ssh-mcp/config.json +6 -0
- package/vendor/mcp/ssh-mcp/package-lock.json +1142 -0
- package/vendor/mcp/ssh-mcp/package.json +18 -0
- package/vendor/mcp/ssh-mcp/src/config.js +140 -0
- package/vendor/mcp/ssh-mcp/src/index.js +130 -0
- package/vendor/mcp/ssh-mcp/src/ssh.js +163 -0
- package/vendor/mcp/sudo-mcp/README.md +51 -0
- package/vendor/mcp/sudo-mcp/config.example.json +28 -0
- package/vendor/mcp/sudo-mcp/config.json +28 -0
- package/vendor/mcp/sudo-mcp/package-lock.json +1145 -0
- package/vendor/mcp/sudo-mcp/package.json +18 -0
- package/vendor/mcp/sudo-mcp/src/config.js +57 -0
- package/vendor/mcp/sudo-mcp/src/index.js +267 -0
- package/vendor/mcp/sudo-mcp/src/runner.js +168 -0
- package/vendor/mcp/web-agent-mcp/package-lock.json +2886 -0
- package/vendor/mcp/web-agent-mcp/package.json +28 -0
- package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/adapter.ts +335 -0
- package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/auth-heuristics.ts +324 -0
- package/vendor/mcp/web-agent-mcp/src/adapters/cloakbrowser/launcher.ts +1340 -0
- package/vendor/mcp/web-agent-mcp/src/config/env.ts +107 -0
- package/vendor/mcp/web-agent-mcp/src/core/action-flow.ts +82 -0
- package/vendor/mcp/web-agent-mcp/src/core/artifact-store.ts +109 -0
- package/vendor/mcp/web-agent-mcp/src/core/errors.ts +108 -0
- package/vendor/mcp/web-agent-mcp/src/core/observation-flow.ts +38 -0
- package/vendor/mcp/web-agent-mcp/src/core/policy-engine.ts +113 -0
- package/vendor/mcp/web-agent-mcp/src/core/retry-policy.ts +42 -0
- package/vendor/mcp/web-agent-mcp/src/core/session-manager.ts +670 -0
- package/vendor/mcp/web-agent-mcp/src/core/session-restart-policy.ts +34 -0
- package/vendor/mcp/web-agent-mcp/src/core/task-history.ts +97 -0
- package/vendor/mcp/web-agent-mcp/src/index.ts +3 -0
- package/vendor/mcp/web-agent-mcp/src/schemas/act.ts +167 -0
- package/vendor/mcp/web-agent-mcp/src/schemas/common.ts +56 -0
- package/vendor/mcp/web-agent-mcp/src/schemas/observe.ts +214 -0
- package/vendor/mcp/web-agent-mcp/src/schemas/page.ts +21 -0
- package/vendor/mcp/web-agent-mcp/src/schemas/policy.ts +42 -0
- package/vendor/mcp/web-agent-mcp/src/schemas/runtime.ts +21 -0
- package/vendor/mcp/web-agent-mcp/src/schemas/session.ts +63 -0
- package/vendor/mcp/web-agent-mcp/src/server.ts +75 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/click.ts +68 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/drag.ts +57 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/enter-code.ts +78 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/fill.ts +65 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/pinch.ts +58 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/press.ts +67 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/shared.ts +73 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/swipe.ts +59 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/wait-for.ts +56 -0
- package/vendor/mcp/web-agent-mcp/src/tools/act/wheel.ts +59 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/a11y.ts +60 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/auth-state.ts +92 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/boxes.ts +66 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/console.ts +67 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/dom.ts +60 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/network.ts +67 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/page-state.ts +93 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/screenshot.ts +73 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/text.ts +70 -0
- package/vendor/mcp/web-agent-mcp/src/tools/observe/wait-for-network.ts +70 -0
- package/vendor/mcp/web-agent-mcp/src/tools/page/navigate.ts +59 -0
- package/vendor/mcp/web-agent-mcp/src/tools/policy/recommend-observation.ts +40 -0
- package/vendor/mcp/web-agent-mcp/src/tools/register-tools.ts +55 -0
- package/vendor/mcp/web-agent-mcp/src/tools/runtime/evaluate-js.ts +83 -0
- package/vendor/mcp/web-agent-mcp/src/tools/session/close.ts +41 -0
- package/vendor/mcp/web-agent-mcp/src/tools/session/create.ts +86 -0
- package/vendor/mcp/web-agent-mcp/src/tools/session/restart.ts +72 -0
- package/vendor/mcp/web-agent-mcp/src/utils/fs.ts +28 -0
- package/vendor/mcp/web-agent-mcp/src/utils/ids.ts +9 -0
- package/vendor/mcp/web-agent-mcp/src/utils/time.ts +7 -0
- package/vendor/mcp/web-agent-mcp/tsconfig.json +22 -0
- package/vendor/skills/editorial-technical-ui/SKILL.md +84 -0
- package/vendor/skills/figma-console/SKILL.md +839 -0
- package/vendor/skills/go-fiber-postgres/SKILL.md +31 -0
- package/vendor/skills/opencode-plugin-dev/SKILL.md +31 -0
- package/vendor/skills/rust-media-desktop/SKILL.md +30 -0
- package/vendor/skills/vue-vite-ui/SKILL.md +31 -0
- package/vendor/skills/web-agent-browser/SKILL.md +140 -0
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "web-agent-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "Web-agent MCP server with a CloakBrowser-backed browser adapter.",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20.0.0"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc -p tsconfig.json",
|
|
12
|
+
"dev": "tsx src/server.ts",
|
|
13
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
14
|
+
"test": "vitest run"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.17.4",
|
|
18
|
+
"cloakbrowser": "^0.3.16",
|
|
19
|
+
"playwright-core": "^1.52.0",
|
|
20
|
+
"zod": "^3.24.2"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^22.13.10",
|
|
24
|
+
"tsx": "^4.19.3",
|
|
25
|
+
"typescript": "^5.8.2",
|
|
26
|
+
"vitest": "^3.0.8"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import type { BrowserContext, Page, ViewportSize } from "playwright-core";
|
|
2
|
+
|
|
3
|
+
export type AdapterProfileMode = "ephemeral" | "persistent";
|
|
4
|
+
export type WaitUntilState = "domcontentloaded" | "load" | "networkidle";
|
|
5
|
+
|
|
6
|
+
export type AdapterSessionHandle = {
|
|
7
|
+
contextId: string;
|
|
8
|
+
pageId: string;
|
|
9
|
+
context: BrowserContext;
|
|
10
|
+
page: Page;
|
|
11
|
+
consoleEntries: AdapterConsoleEntry[];
|
|
12
|
+
networkEntries: AdapterNetworkEntry[];
|
|
13
|
+
profileMode: AdapterProfileMode;
|
|
14
|
+
locale?: string;
|
|
15
|
+
viewport: ViewportSize;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type AdapterSessionCreateInput = {
|
|
19
|
+
sessionId: string;
|
|
20
|
+
profileMode: AdapterProfileMode;
|
|
21
|
+
locale: string;
|
|
22
|
+
timezoneId?: string;
|
|
23
|
+
userDataDir?: string;
|
|
24
|
+
profileDirectory?: string;
|
|
25
|
+
humanize: boolean;
|
|
26
|
+
launchArgs: string[];
|
|
27
|
+
viewport: ViewportSize;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type AdapterNavigationResult = {
|
|
31
|
+
pageId: string;
|
|
32
|
+
requestedUrl: string;
|
|
33
|
+
finalUrl: string;
|
|
34
|
+
title?: string;
|
|
35
|
+
elapsedMs: number;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export type AdapterA11yResult = {
|
|
39
|
+
url: string;
|
|
40
|
+
title?: string;
|
|
41
|
+
tree: {
|
|
42
|
+
role: string;
|
|
43
|
+
name?: string;
|
|
44
|
+
children: Array<{
|
|
45
|
+
role: string;
|
|
46
|
+
name?: string;
|
|
47
|
+
tag?: string;
|
|
48
|
+
text?: string;
|
|
49
|
+
}>;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type AdapterDomResult = {
|
|
54
|
+
url: string;
|
|
55
|
+
title?: string;
|
|
56
|
+
summary: {
|
|
57
|
+
headings: string[];
|
|
58
|
+
links: number;
|
|
59
|
+
buttons: number;
|
|
60
|
+
forms: number;
|
|
61
|
+
inputs: number;
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type AdapterTextResult = {
|
|
66
|
+
url: string;
|
|
67
|
+
title?: string;
|
|
68
|
+
format: "text" | "markdown";
|
|
69
|
+
content: string;
|
|
70
|
+
truncated: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export type AdapterInteractiveElementSummary = {
|
|
74
|
+
tag: string;
|
|
75
|
+
type?: string;
|
|
76
|
+
id?: string;
|
|
77
|
+
name?: string;
|
|
78
|
+
placeholder?: string;
|
|
79
|
+
text?: string;
|
|
80
|
+
autocomplete?: string;
|
|
81
|
+
visible: boolean;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export type AdapterFrameSummary = {
|
|
85
|
+
index: number;
|
|
86
|
+
name?: string;
|
|
87
|
+
url: string;
|
|
88
|
+
title?: string;
|
|
89
|
+
text_preview: string;
|
|
90
|
+
truncated: boolean;
|
|
91
|
+
input_count: number;
|
|
92
|
+
button_count: number;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type AdapterPageStateResult = {
|
|
96
|
+
url: string;
|
|
97
|
+
title?: string;
|
|
98
|
+
text: string;
|
|
99
|
+
truncated: boolean;
|
|
100
|
+
dom: AdapterDomResult["summary"];
|
|
101
|
+
inputs: AdapterInteractiveElementSummary[];
|
|
102
|
+
buttons: AdapterInteractiveElementSummary[];
|
|
103
|
+
frames: AdapterFrameSummary[];
|
|
104
|
+
recentNetwork: AdapterNetworkEntry[];
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
export type AdapterAuthState =
|
|
108
|
+
| "email_prompt"
|
|
109
|
+
| "password_prompt"
|
|
110
|
+
| "phone_selection"
|
|
111
|
+
| "verification_code"
|
|
112
|
+
| "trust_prompt"
|
|
113
|
+
| "authenticated"
|
|
114
|
+
| "unknown";
|
|
115
|
+
|
|
116
|
+
export type AdapterAuthStateResult = {
|
|
117
|
+
url: string;
|
|
118
|
+
title?: string;
|
|
119
|
+
state: AdapterAuthState;
|
|
120
|
+
confidence: "high" | "medium" | "low";
|
|
121
|
+
summary: string;
|
|
122
|
+
evidence: string[];
|
|
123
|
+
suggestedSelectors: string[];
|
|
124
|
+
frames: AdapterFrameSummary[];
|
|
125
|
+
recentNetwork: AdapterNetworkEntry[];
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export type AdapterScreenshotResult = {
|
|
129
|
+
url: string;
|
|
130
|
+
title?: string;
|
|
131
|
+
bytes: Buffer;
|
|
132
|
+
mimeType: string;
|
|
133
|
+
width?: number;
|
|
134
|
+
height?: number;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
export type AdapterActionResult = {
|
|
138
|
+
url: string;
|
|
139
|
+
title?: string;
|
|
140
|
+
verificationHint?: string;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export type AdapterElementBox = {
|
|
144
|
+
selector: string;
|
|
145
|
+
x: number;
|
|
146
|
+
y: number;
|
|
147
|
+
width: number;
|
|
148
|
+
height: number;
|
|
149
|
+
visible: boolean;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export type AdapterConsoleEntry = {
|
|
153
|
+
type: string;
|
|
154
|
+
text: string;
|
|
155
|
+
location?: {
|
|
156
|
+
url?: string;
|
|
157
|
+
lineNumber?: number;
|
|
158
|
+
columnNumber?: number;
|
|
159
|
+
};
|
|
160
|
+
timestamp: string;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
export type AdapterNetworkEntry = {
|
|
164
|
+
url: string;
|
|
165
|
+
method: string;
|
|
166
|
+
status?: number;
|
|
167
|
+
resourceType: string;
|
|
168
|
+
outcome: "response" | "failed";
|
|
169
|
+
failureText?: string;
|
|
170
|
+
timestamp: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
export type AdapterEvaluateResult = {
|
|
174
|
+
url: string;
|
|
175
|
+
title?: string;
|
|
176
|
+
value: unknown;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export type AdapterWaitForNetworkResult = {
|
|
180
|
+
url: string;
|
|
181
|
+
title?: string;
|
|
182
|
+
entry: AdapterNetworkEntry;
|
|
183
|
+
elapsedMs: number;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
export interface CloakBrowserAdapter {
|
|
187
|
+
createSession(
|
|
188
|
+
input: AdapterSessionCreateInput,
|
|
189
|
+
): Promise<AdapterSessionHandle>;
|
|
190
|
+
closeSession(session: AdapterSessionHandle): Promise<void>;
|
|
191
|
+
navigate(
|
|
192
|
+
session: AdapterSessionHandle,
|
|
193
|
+
url: string,
|
|
194
|
+
waitUntil: WaitUntilState,
|
|
195
|
+
): Promise<AdapterNavigationResult>;
|
|
196
|
+
observeA11y(session: AdapterSessionHandle): Promise<AdapterA11yResult>;
|
|
197
|
+
observeDom(session: AdapterSessionHandle): Promise<AdapterDomResult>;
|
|
198
|
+
observeText(
|
|
199
|
+
session: AdapterSessionHandle,
|
|
200
|
+
format: "text" | "markdown",
|
|
201
|
+
): Promise<AdapterTextResult>;
|
|
202
|
+
inspectPageState(
|
|
203
|
+
session: AdapterSessionHandle,
|
|
204
|
+
recentNetworkLimit: number,
|
|
205
|
+
): Promise<AdapterPageStateResult>;
|
|
206
|
+
inspectAuthState(
|
|
207
|
+
session: AdapterSessionHandle,
|
|
208
|
+
recentNetworkLimit: number,
|
|
209
|
+
): Promise<AdapterAuthStateResult>;
|
|
210
|
+
takeScreenshot(
|
|
211
|
+
session: AdapterSessionHandle,
|
|
212
|
+
mode: "viewport" | "full" | "element",
|
|
213
|
+
format: "png" | "jpeg",
|
|
214
|
+
quality?: number,
|
|
215
|
+
selector?: string,
|
|
216
|
+
): Promise<AdapterScreenshotResult>;
|
|
217
|
+
observeBoxes(
|
|
218
|
+
session: AdapterSessionHandle,
|
|
219
|
+
selectors: string[],
|
|
220
|
+
): Promise<AdapterElementBox[]>;
|
|
221
|
+
observeConsole(
|
|
222
|
+
session: AdapterSessionHandle,
|
|
223
|
+
limit: number,
|
|
224
|
+
): Promise<AdapterConsoleEntry[]>;
|
|
225
|
+
observeNetwork(
|
|
226
|
+
session: AdapterSessionHandle,
|
|
227
|
+
limit: number,
|
|
228
|
+
): Promise<AdapterNetworkEntry[]>;
|
|
229
|
+
waitForNetwork(
|
|
230
|
+
session: AdapterSessionHandle,
|
|
231
|
+
input: {
|
|
232
|
+
urlPattern: string;
|
|
233
|
+
useRegex: boolean;
|
|
234
|
+
status?: number;
|
|
235
|
+
outcome?: AdapterNetworkEntry["outcome"];
|
|
236
|
+
timeoutMs: number;
|
|
237
|
+
pollIntervalMs: number;
|
|
238
|
+
},
|
|
239
|
+
): Promise<AdapterWaitForNetworkResult>;
|
|
240
|
+
evaluateJs(
|
|
241
|
+
session: AdapterSessionHandle,
|
|
242
|
+
input: {
|
|
243
|
+
expression: string;
|
|
244
|
+
awaitPromise: boolean;
|
|
245
|
+
},
|
|
246
|
+
): Promise<AdapterEvaluateResult>;
|
|
247
|
+
click(
|
|
248
|
+
session: AdapterSessionHandle,
|
|
249
|
+
input: {
|
|
250
|
+
selector: string;
|
|
251
|
+
frameSelector?: string;
|
|
252
|
+
button: "left" | "right" | "middle";
|
|
253
|
+
clickCount: number;
|
|
254
|
+
timeoutMs?: number;
|
|
255
|
+
},
|
|
256
|
+
): Promise<AdapterActionResult>;
|
|
257
|
+
fill(
|
|
258
|
+
session: AdapterSessionHandle,
|
|
259
|
+
input: {
|
|
260
|
+
selector: string;
|
|
261
|
+
frameSelector?: string;
|
|
262
|
+
value: string;
|
|
263
|
+
clearFirst: boolean;
|
|
264
|
+
timeoutMs?: number;
|
|
265
|
+
},
|
|
266
|
+
): Promise<AdapterActionResult>;
|
|
267
|
+
enterCode(
|
|
268
|
+
session: AdapterSessionHandle,
|
|
269
|
+
input: {
|
|
270
|
+
code: string;
|
|
271
|
+
selector?: string;
|
|
272
|
+
frameSelector?: string;
|
|
273
|
+
submit: boolean;
|
|
274
|
+
timeoutMs?: number;
|
|
275
|
+
},
|
|
276
|
+
): Promise<AdapterActionResult>;
|
|
277
|
+
press(
|
|
278
|
+
session: AdapterSessionHandle,
|
|
279
|
+
input: {
|
|
280
|
+
key: string;
|
|
281
|
+
selector?: string;
|
|
282
|
+
frameSelector?: string;
|
|
283
|
+
timeoutMs?: number;
|
|
284
|
+
},
|
|
285
|
+
): Promise<AdapterActionResult>;
|
|
286
|
+
waitFor(
|
|
287
|
+
session: AdapterSessionHandle,
|
|
288
|
+
input: {
|
|
289
|
+
selector?: string;
|
|
290
|
+
text?: string;
|
|
291
|
+
timeoutMs: number;
|
|
292
|
+
},
|
|
293
|
+
): Promise<AdapterActionResult>;
|
|
294
|
+
wheel(
|
|
295
|
+
session: AdapterSessionHandle,
|
|
296
|
+
input: {
|
|
297
|
+
selector?: string;
|
|
298
|
+
deltaX: number;
|
|
299
|
+
deltaY: number;
|
|
300
|
+
steps: number;
|
|
301
|
+
stepDelayMs: number;
|
|
302
|
+
timeoutMs?: number;
|
|
303
|
+
},
|
|
304
|
+
): Promise<AdapterActionResult>;
|
|
305
|
+
drag(
|
|
306
|
+
session: AdapterSessionHandle,
|
|
307
|
+
input: {
|
|
308
|
+
fromSelector: string;
|
|
309
|
+
toSelector: string;
|
|
310
|
+
steps: number;
|
|
311
|
+
timeoutMs?: number;
|
|
312
|
+
},
|
|
313
|
+
): Promise<AdapterActionResult>;
|
|
314
|
+
swipe(
|
|
315
|
+
session: AdapterSessionHandle,
|
|
316
|
+
input: {
|
|
317
|
+
selector?: string;
|
|
318
|
+
startX?: number;
|
|
319
|
+
startY?: number;
|
|
320
|
+
deltaX: number;
|
|
321
|
+
deltaY: number;
|
|
322
|
+
speed: number;
|
|
323
|
+
},
|
|
324
|
+
): Promise<AdapterActionResult>;
|
|
325
|
+
pinch(
|
|
326
|
+
session: AdapterSessionHandle,
|
|
327
|
+
input: {
|
|
328
|
+
selector?: string;
|
|
329
|
+
centerX?: number;
|
|
330
|
+
centerY?: number;
|
|
331
|
+
scaleFactor: number;
|
|
332
|
+
speed: number;
|
|
333
|
+
},
|
|
334
|
+
): Promise<AdapterActionResult>;
|
|
335
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AdapterAuthStateResult,
|
|
3
|
+
AdapterInteractiveElementSummary,
|
|
4
|
+
AdapterNetworkEntry
|
|
5
|
+
} from "./adapter.js";
|
|
6
|
+
|
|
7
|
+
export type AuthFrameInspection = {
|
|
8
|
+
index: number;
|
|
9
|
+
name?: string;
|
|
10
|
+
url: string;
|
|
11
|
+
title?: string;
|
|
12
|
+
text: string;
|
|
13
|
+
inputs: AdapterInteractiveElementSummary[];
|
|
14
|
+
buttons: AdapterInteractiveElementSummary[];
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
type AuthDocumentInspection = {
|
|
18
|
+
kind: "page" | "frame";
|
|
19
|
+
url: string;
|
|
20
|
+
title?: string;
|
|
21
|
+
text: string;
|
|
22
|
+
inputs: AdapterInteractiveElementSummary[];
|
|
23
|
+
buttons: AdapterInteractiveElementSummary[];
|
|
24
|
+
frame?: Pick<AuthFrameInspection, "index" | "name">;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type AuthStateClassification = Pick<
|
|
28
|
+
AdapterAuthStateResult,
|
|
29
|
+
"state" | "confidence" | "summary" | "evidence" | "suggestedSelectors"
|
|
30
|
+
>;
|
|
31
|
+
|
|
32
|
+
export function normalizeAuthText(text: string | undefined) {
|
|
33
|
+
return text?.replace(/\s+/g, " ").trim() ?? "";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function toLowerText(text: string | undefined) {
|
|
37
|
+
return normalizeAuthText(text).toLowerCase();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function getVisibleInputs(inputs: AdapterInteractiveElementSummary[]) {
|
|
41
|
+
return inputs.filter((input) => input.visible !== false);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getButtonTexts(buttons: AdapterInteractiveElementSummary[]) {
|
|
45
|
+
return buttons
|
|
46
|
+
.map((button) => normalizeAuthText(button.text))
|
|
47
|
+
.filter((text): text is string => Boolean(text));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isAppleAuthDocument(document: AuthDocumentInspection) {
|
|
51
|
+
return /apple\.com/i.test(document.url) || /apple/i.test(document.title ?? "");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function hasVerificationCodeInputs(document: AuthDocumentInspection) {
|
|
55
|
+
const visibleInputs = getVisibleInputs(document.inputs);
|
|
56
|
+
const otpInputs = visibleInputs.filter((input) => {
|
|
57
|
+
const autocomplete = input.autocomplete?.toLowerCase();
|
|
58
|
+
const type = input.type?.toLowerCase();
|
|
59
|
+
return (
|
|
60
|
+
autocomplete === "one-time-code" ||
|
|
61
|
+
type === "tel" ||
|
|
62
|
+
type === "number" ||
|
|
63
|
+
type === "otp" ||
|
|
64
|
+
type === "text"
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
return otpInputs.length >= 4;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasPhoneChallengeButtons(document: AuthDocumentInspection) {
|
|
72
|
+
const buttonTexts = getButtonTexts(document.buttons);
|
|
73
|
+
const maskedOptions = buttonTexts.filter((text) => /[•*x]/i.test(text) && /\b\d{2}\b/.test(text));
|
|
74
|
+
const combined = toLowerText([document.text, ...buttonTexts].join(" "));
|
|
75
|
+
|
|
76
|
+
return maskedOptions.length >= 2 && /(sms|text|telefon|phone|mesaj)/i.test(combined);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function isTrustPrompt(document: AuthDocumentInspection) {
|
|
80
|
+
const combined = toLowerText([document.title, document.text, ...getButtonTexts(document.buttons)].join(" "));
|
|
81
|
+
const hasTrustCopy =
|
|
82
|
+
/trust this browser|bu tarayiciya guven|bu tarayıcıya güven|trusted browser/i.test(combined);
|
|
83
|
+
const hasTrustButtons =
|
|
84
|
+
/(guvenme|güvenme|simdi degil|şimdi değil|trust|don't trust|not now)/i.test(combined);
|
|
85
|
+
|
|
86
|
+
return hasTrustCopy || (hasTrustButtons && /(guven|güven|trust)/i.test(combined));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function hasVisiblePasswordInput(document: AuthDocumentInspection) {
|
|
90
|
+
return getVisibleInputs(document.inputs).some((input) => input.type?.toLowerCase() === "password");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function hasVisibleIdentityInput(document: AuthDocumentInspection) {
|
|
94
|
+
return getVisibleInputs(document.inputs).some((input) => {
|
|
95
|
+
const type = input.type?.toLowerCase();
|
|
96
|
+
return type === "email" || type === "text" || type === "tel";
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function scoreAuthDocument(document: AuthDocumentInspection) {
|
|
101
|
+
const text = toLowerText([document.title, document.text, ...getButtonTexts(document.buttons)].join(" "));
|
|
102
|
+
let score = 0;
|
|
103
|
+
|
|
104
|
+
if (hasVerificationCodeInputs(document)) {
|
|
105
|
+
score += 8;
|
|
106
|
+
}
|
|
107
|
+
if (hasPhoneChallengeButtons(document)) {
|
|
108
|
+
score += 7;
|
|
109
|
+
}
|
|
110
|
+
if (isTrustPrompt(document)) {
|
|
111
|
+
score += 7;
|
|
112
|
+
}
|
|
113
|
+
if (hasVisiblePasswordInput(document)) {
|
|
114
|
+
score += 6;
|
|
115
|
+
}
|
|
116
|
+
if (hasVisibleIdentityInput(document)) {
|
|
117
|
+
score += 4;
|
|
118
|
+
}
|
|
119
|
+
if (/sign in|giris yap|giriş yap|apple developer/i.test(text)) {
|
|
120
|
+
score += 2;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (document.kind === "frame" && score > 0) {
|
|
124
|
+
score += 1;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return score;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function pickPrimaryDocument(documents: AuthDocumentInspection[]) {
|
|
131
|
+
if (documents.length === 0) {
|
|
132
|
+
throw new Error("Expected at least one auth document to classify");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return [...documents].sort((left, right) => scoreAuthDocument(right) - scoreAuthDocument(left))[0] ?? documents[0]!;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getSuggestedSelectors(state: AuthStateClassification["state"], document: AuthDocumentInspection) {
|
|
139
|
+
const isApple = isAppleAuthDocument(document);
|
|
140
|
+
const framePrefix = isApple && document.kind === "frame"
|
|
141
|
+
? "iframe#aid-auth-widget-iFrame >> internal:control=enter-frame >> "
|
|
142
|
+
: "";
|
|
143
|
+
|
|
144
|
+
switch (state) {
|
|
145
|
+
case "verification_code":
|
|
146
|
+
return isApple && document.kind === "frame"
|
|
147
|
+
? [
|
|
148
|
+
`${framePrefix}input[autocomplete='one-time-code']`,
|
|
149
|
+
`${framePrefix}input[inputmode='numeric']`,
|
|
150
|
+
`${framePrefix}input[type='tel']`
|
|
151
|
+
]
|
|
152
|
+
: ["input[autocomplete='one-time-code']", "input[inputmode='numeric']", "input[type='tel']"];
|
|
153
|
+
case "phone_selection":
|
|
154
|
+
return isApple && document.kind === "frame"
|
|
155
|
+
? [`${framePrefix}text=/SMS|Text|Phone|Telefon/`]
|
|
156
|
+
: ["text=/SMS|Text|Phone|Telefon/"];
|
|
157
|
+
case "trust_prompt":
|
|
158
|
+
return isApple && document.kind === "frame"
|
|
159
|
+
? [`${framePrefix}text=/Trust|Güven|Guven/`]
|
|
160
|
+
: ["text=/Trust|Güven|Guven/"];
|
|
161
|
+
case "password_prompt":
|
|
162
|
+
return isApple && document.kind === "frame"
|
|
163
|
+
? [`${framePrefix}#password_text_field`, `${framePrefix}input[type='password']`]
|
|
164
|
+
: ["input[type='password']"];
|
|
165
|
+
case "email_prompt":
|
|
166
|
+
return isApple && document.kind === "frame"
|
|
167
|
+
? [`${framePrefix}#account_name_text_field`, `${framePrefix}input[type='email']`, `${framePrefix}input[type='text']`]
|
|
168
|
+
: ["input[type='email']", "input[type='text']"];
|
|
169
|
+
default:
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function classifyAuthStateSnapshot(input: {
|
|
175
|
+
pageUrl: string;
|
|
176
|
+
pageTitle?: string;
|
|
177
|
+
pageText: string;
|
|
178
|
+
pageInputs: AdapterInteractiveElementSummary[];
|
|
179
|
+
pageButtons: AdapterInteractiveElementSummary[];
|
|
180
|
+
frames: AuthFrameInspection[];
|
|
181
|
+
recentNetwork: AdapterNetworkEntry[];
|
|
182
|
+
}): AuthStateClassification {
|
|
183
|
+
const evidence: string[] = [];
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
input.pageUrl.includes("developer.apple.com/account") ||
|
|
187
|
+
input.recentNetwork.some(
|
|
188
|
+
(entry) => entry.status === 200 && /developer\.apple\.com\/services-account\//i.test(entry.url)
|
|
189
|
+
)
|
|
190
|
+
) {
|
|
191
|
+
evidence.push("authenticated developer account endpoints loaded");
|
|
192
|
+
return {
|
|
193
|
+
state: "authenticated",
|
|
194
|
+
confidence: "high",
|
|
195
|
+
summary: "The authenticated destination page is loaded.",
|
|
196
|
+
evidence,
|
|
197
|
+
suggestedSelectors: []
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const documents: AuthDocumentInspection[] = [
|
|
202
|
+
{
|
|
203
|
+
kind: "page",
|
|
204
|
+
url: input.pageUrl,
|
|
205
|
+
title: input.pageTitle,
|
|
206
|
+
text: input.pageText,
|
|
207
|
+
inputs: input.pageInputs,
|
|
208
|
+
buttons: input.pageButtons
|
|
209
|
+
},
|
|
210
|
+
...input.frames.map((frame) => ({
|
|
211
|
+
kind: "frame" as const,
|
|
212
|
+
url: frame.url,
|
|
213
|
+
title: frame.title,
|
|
214
|
+
text: frame.text,
|
|
215
|
+
inputs: frame.inputs,
|
|
216
|
+
buttons: frame.buttons,
|
|
217
|
+
frame: {
|
|
218
|
+
index: frame.index,
|
|
219
|
+
name: frame.name
|
|
220
|
+
}
|
|
221
|
+
}))
|
|
222
|
+
];
|
|
223
|
+
const primary = pickPrimaryDocument(documents);
|
|
224
|
+
const primaryText = toLowerText([primary.title, primary.text, ...getButtonTexts(primary.buttons)].join(" "));
|
|
225
|
+
const networkText = input.recentNetwork.map((entry) => entry.url.toLowerCase()).join(" ");
|
|
226
|
+
|
|
227
|
+
if (primary.kind === "frame") {
|
|
228
|
+
evidence.push(`same-origin auth iframe inspected${primary.frame?.name ? ` (${primary.frame.name})` : ""}`);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (isTrustPrompt(primary)) {
|
|
232
|
+
evidence.push("browser trust prompt detected from live DOM");
|
|
233
|
+
return {
|
|
234
|
+
state: "trust_prompt",
|
|
235
|
+
confidence: "high",
|
|
236
|
+
summary: "The flow is asking whether this browser should be trusted.",
|
|
237
|
+
evidence,
|
|
238
|
+
suggestedSelectors: getSuggestedSelectors("trust_prompt", primary)
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (
|
|
243
|
+
hasPhoneChallengeButtons(primary) ||
|
|
244
|
+
/telefon numarasi secin|telefon numarası seçin|which phone|receive the code|texted to|sms|phone number/i.test(primaryText)
|
|
245
|
+
) {
|
|
246
|
+
evidence.push("phone challenge options detected from live DOM");
|
|
247
|
+
return {
|
|
248
|
+
state: "phone_selection",
|
|
249
|
+
confidence: "high",
|
|
250
|
+
summary: "The flow is asking which phone or delivery method should receive the code.",
|
|
251
|
+
evidence,
|
|
252
|
+
suggestedSelectors: getSuggestedSelectors("phone_selection", primary)
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (
|
|
257
|
+
hasVerificationCodeInputs(primary) ||
|
|
258
|
+
/verification code|dogrulama kodu|doğrulama kodu|security code|enter the code|iki faktorlu|iki faktörlü/i.test(primaryText)
|
|
259
|
+
) {
|
|
260
|
+
evidence.push("verification code step detected from live DOM");
|
|
261
|
+
return {
|
|
262
|
+
state: "verification_code",
|
|
263
|
+
confidence: "high",
|
|
264
|
+
summary: "The flow is waiting for a one-time verification code.",
|
|
265
|
+
evidence,
|
|
266
|
+
suggestedSelectors: getSuggestedSelectors("verification_code", primary)
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (hasVisiblePasswordInput(primary) || /password|parola/i.test(primaryText)) {
|
|
271
|
+
evidence.push("password prompt detected from live DOM");
|
|
272
|
+
return {
|
|
273
|
+
state: "password_prompt",
|
|
274
|
+
confidence: hasVisiblePasswordInput(primary) ? "high" : "medium",
|
|
275
|
+
summary: "The flow is asking for an account password.",
|
|
276
|
+
evidence,
|
|
277
|
+
suggestedSelectors: getSuggestedSelectors("password_prompt", primary)
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
if (
|
|
282
|
+
hasVisibleIdentityInput(primary) ||
|
|
283
|
+
/email|e-posta|telefon|phone number|sign in|giris yap|giriş yap/i.test(primaryText)
|
|
284
|
+
) {
|
|
285
|
+
evidence.push("identity prompt detected from live DOM");
|
|
286
|
+
return {
|
|
287
|
+
state: "email_prompt",
|
|
288
|
+
confidence: hasVisibleIdentityInput(primary) ? "high" : "medium",
|
|
289
|
+
summary: "The flow is asking for an email address or phone number.",
|
|
290
|
+
evidence,
|
|
291
|
+
suggestedSelectors: getSuggestedSelectors("email_prompt", primary)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (/verify\/phone|one-time-code|hsa2/i.test(networkText)) {
|
|
296
|
+
evidence.push("verification code hints detected from recent network only");
|
|
297
|
+
return {
|
|
298
|
+
state: "verification_code",
|
|
299
|
+
confidence: "low",
|
|
300
|
+
summary: "The flow may be waiting for a one-time verification code.",
|
|
301
|
+
evidence,
|
|
302
|
+
suggestedSelectors: getSuggestedSelectors("verification_code", primary)
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (/trusteddevice|trusted\/phone|sendcode|delivery/i.test(networkText)) {
|
|
307
|
+
evidence.push("phone challenge hints detected from recent network only");
|
|
308
|
+
return {
|
|
309
|
+
state: "phone_selection",
|
|
310
|
+
confidence: "low",
|
|
311
|
+
summary: "The flow may be asking for a phone or code delivery method.",
|
|
312
|
+
evidence,
|
|
313
|
+
suggestedSelectors: getSuggestedSelectors("phone_selection", primary)
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return {
|
|
318
|
+
state: "unknown",
|
|
319
|
+
confidence: "low",
|
|
320
|
+
summary: "The flow could not be classified from the current page and recent network activity.",
|
|
321
|
+
evidence,
|
|
322
|
+
suggestedSelectors: []
|
|
323
|
+
};
|
|
324
|
+
}
|