@wonderwhy-er/desktop-commander 0.2.36 → 0.2.38
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 +240 -100
- package/dist/command-manager.js +6 -3
- package/dist/config-field-definitions.d.ts +41 -0
- package/dist/config-field-definitions.js +37 -0
- package/dist/config-manager.d.ts +2 -0
- package/dist/config-manager.js +22 -2
- package/dist/handlers/filesystem-handlers.js +6 -11
- package/dist/handlers/macos-control-handlers.d.ts +16 -0
- package/dist/handlers/macos-control-handlers.js +81 -0
- package/dist/lib.d.ts +10 -0
- package/dist/lib.js +10 -0
- package/dist/remote-device/remote-channel.d.ts +8 -3
- package/dist/remote-device/remote-channel.js +68 -21
- package/dist/search-manager.d.ts +13 -0
- package/dist/search-manager.js +146 -0
- package/dist/server.js +29 -1
- package/dist/test-docx.d.ts +1 -0
- package/dist/tools/config.d.ts +71 -0
- package/dist/tools/config.js +117 -2
- package/dist/tools/docx/builders/table.d.ts +2 -0
- package/dist/tools/docx/builders/table.js +60 -16
- package/dist/tools/docx/dom.d.ts +74 -1
- package/dist/tools/docx/dom.js +221 -1
- package/dist/tools/docx/index.d.ts +2 -2
- package/dist/tools/docx/ops/index.js +3 -0
- package/dist/tools/docx/ops/replace-paragraph-text-exact.d.ts +15 -3
- package/dist/tools/docx/ops/replace-paragraph-text-exact.js +25 -10
- package/dist/tools/docx/ops/replace-table-cell-text.d.ts +25 -0
- package/dist/tools/docx/ops/replace-table-cell-text.js +85 -0
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.d.ts +2 -1
- package/dist/tools/docx/ops/set-color-for-paragraph-exact.js +9 -8
- package/dist/tools/docx/ops/set-color-for-style.d.ts +4 -0
- package/dist/tools/docx/ops/set-color-for-style.js +11 -7
- package/dist/tools/docx/ops/table-set-cell-text.js +8 -40
- package/dist/tools/docx/read.d.ts +2 -2
- package/dist/tools/docx/read.js +137 -17
- package/dist/tools/docx/types.d.ts +32 -3
- package/dist/tools/docx/xml-view-test.d.ts +1 -0
- package/dist/tools/docx/xml-view-test.js +63 -0
- package/dist/tools/docx/xml-view.d.ts +56 -0
- package/dist/tools/docx/xml-view.js +169 -0
- package/dist/tools/edit.js +57 -27
- package/dist/tools/macos-control/ax-adapter.d.ts +55 -0
- package/dist/tools/macos-control/ax-adapter.js +438 -0
- package/dist/tools/macos-control/cdp-adapter.d.ts +23 -0
- package/dist/tools/macos-control/cdp-adapter.js +402 -0
- package/dist/tools/macos-control/orchestrator.d.ts +77 -0
- package/dist/tools/macos-control/orchestrator.js +136 -0
- package/dist/tools/macos-control/role-aliases.d.ts +5 -0
- package/dist/tools/macos-control/role-aliases.js +34 -0
- package/dist/tools/macos-control/types.d.ts +129 -0
- package/dist/tools/macos-control/types.js +1 -0
- package/dist/tools/schemas.d.ts +3 -0
- package/dist/tools/schemas.js +2 -1
- package/dist/types.d.ts +0 -1
- package/dist/ui/config-editor/config-editor-runtime.js +14181 -0
- package/dist/ui/config-editor/index.html +13 -0
- package/dist/ui/config-editor/src/app.d.ts +43 -0
- package/dist/ui/config-editor/src/app.js +840 -0
- package/dist/ui/config-editor/src/array-modal.d.ts +19 -0
- package/dist/ui/config-editor/src/array-modal.js +185 -0
- package/dist/ui/config-editor/src/main.d.ts +1 -0
- package/dist/ui/config-editor/src/main.js +2 -0
- package/dist/ui/config-editor/styles.css +586 -0
- package/dist/ui/file-preview/preview-runtime.js +13337 -752
- package/dist/ui/file-preview/shared/preview-file-types.js +3 -1
- package/dist/ui/file-preview/src/app.d.ts +5 -1
- package/dist/ui/file-preview/src/app.js +114 -200
- package/dist/ui/file-preview/src/components/html-renderer.d.ts +1 -5
- package/dist/ui/file-preview/src/components/html-renderer.js +11 -27
- package/dist/ui/file-preview/styles.css +117 -83
- package/dist/ui/resources.d.ts +7 -0
- package/dist/ui/resources.js +16 -2
- package/dist/ui/shared/compact-row.d.ts +11 -0
- package/dist/ui/shared/compact-row.js +18 -0
- package/dist/ui/shared/host-context.d.ts +15 -0
- package/dist/ui/shared/host-context.js +51 -0
- package/dist/ui/shared/tool-bridge.d.ts +30 -0
- package/dist/ui/shared/tool-bridge.js +137 -0
- package/dist/ui/shared/tool-shell.d.ts +9 -0
- package/dist/ui/shared/tool-shell.js +46 -4
- package/dist/ui/shared/ui-event-tracker.d.ts +9 -0
- package/dist/ui/shared/ui-event-tracker.js +27 -0
- package/dist/utils/capture.js +173 -11
- package/dist/utils/files/base.d.ts +3 -1
- package/dist/utils/files/docx.d.ts +28 -15
- package/dist/utils/files/docx.js +622 -88
- package/dist/utils/files/factory.d.ts +6 -5
- package/dist/utils/files/factory.js +18 -6
- package/dist/utils/system-info.js +1 -1
- package/dist/utils/usageTracker.js +5 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +8 -3
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { AxBatchCommand, AxBatchResult, AxAppInfo, AxElement, AxStatus, MacosControlResult } from './types.js';
|
|
2
|
+
export declare class AxAdapter {
|
|
3
|
+
private helperPathCache;
|
|
4
|
+
private signatures;
|
|
5
|
+
private resolveHelperPath;
|
|
6
|
+
private runHelper;
|
|
7
|
+
private rememberElements;
|
|
8
|
+
private elementScore;
|
|
9
|
+
private fallbackRefindElement;
|
|
10
|
+
status(): Promise<MacosControlResult<AxStatus>>;
|
|
11
|
+
listApps(): Promise<MacosControlResult<AxAppInfo[]>>;
|
|
12
|
+
listElements(args: {
|
|
13
|
+
scope?: 'top_window' | 'app' | 'all';
|
|
14
|
+
app?: string;
|
|
15
|
+
text?: string;
|
|
16
|
+
role?: string;
|
|
17
|
+
depth?: number;
|
|
18
|
+
limit?: number;
|
|
19
|
+
}): Promise<MacosControlResult<AxElement[]>>;
|
|
20
|
+
find(args: {
|
|
21
|
+
app: string;
|
|
22
|
+
text?: string;
|
|
23
|
+
role?: string;
|
|
24
|
+
depth?: number;
|
|
25
|
+
limit?: number;
|
|
26
|
+
index?: number;
|
|
27
|
+
}): Promise<MacosControlResult<AxElement[]>>;
|
|
28
|
+
clickById(id: string, appHint?: string): Promise<MacosControlResult<Record<string, unknown>>>;
|
|
29
|
+
typeText(text: string): Promise<MacosControlResult<Record<string, unknown>>>;
|
|
30
|
+
pressKey(key: string, modifiers?: string[]): Promise<MacosControlResult<Record<string, unknown>>>;
|
|
31
|
+
activate(app: string): Promise<MacosControlResult<Record<string, unknown>>>;
|
|
32
|
+
waitFor(args: {
|
|
33
|
+
app: string;
|
|
34
|
+
text: string;
|
|
35
|
+
role?: string;
|
|
36
|
+
timeout_ms?: number;
|
|
37
|
+
depth?: number;
|
|
38
|
+
}): Promise<MacosControlResult<AxElement>>;
|
|
39
|
+
getState(args: {
|
|
40
|
+
app: string;
|
|
41
|
+
text?: string;
|
|
42
|
+
role?: string;
|
|
43
|
+
depth?: number;
|
|
44
|
+
limit?: number;
|
|
45
|
+
index?: number;
|
|
46
|
+
}): Promise<MacosControlResult<Record<string, unknown>>>;
|
|
47
|
+
scroll(args: {
|
|
48
|
+
x: number;
|
|
49
|
+
y: number;
|
|
50
|
+
direction?: 'up' | 'down';
|
|
51
|
+
amount?: number;
|
|
52
|
+
}): Promise<MacosControlResult<Record<string, unknown>>>;
|
|
53
|
+
batch(commands: AxBatchCommand[], stopOnError: boolean): Promise<MacosControlResult<AxBatchResult>>;
|
|
54
|
+
}
|
|
55
|
+
export declare const axAdapter: AxAdapter;
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { access } from 'fs/promises';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { expandRoleAlias } from './role-aliases.js';
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = path.dirname(__filename);
|
|
9
|
+
const PROJECT_ROOT = path.resolve(__dirname, '../../..');
|
|
10
|
+
function normalizeHelperError(code) {
|
|
11
|
+
switch (code) {
|
|
12
|
+
case 'UNSUPPORTED_PLATFORM':
|
|
13
|
+
return 'UNSUPPORTED_PLATFORM';
|
|
14
|
+
case 'HELPER_NOT_FOUND':
|
|
15
|
+
return 'HELPER_NOT_FOUND';
|
|
16
|
+
case 'PERMISSION_DENIED':
|
|
17
|
+
return 'PERMISSION_DENIED';
|
|
18
|
+
case 'INVALID_ARGUMENT':
|
|
19
|
+
return 'INVALID_ARGUMENT';
|
|
20
|
+
case 'NOT_FOUND':
|
|
21
|
+
return 'NOT_FOUND';
|
|
22
|
+
case 'TIMEOUT':
|
|
23
|
+
return 'TIMEOUT';
|
|
24
|
+
case 'ACTION_FAILED':
|
|
25
|
+
return 'ACTION_FAILED';
|
|
26
|
+
default:
|
|
27
|
+
return 'INTERNAL_ERROR';
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function isExecutable(filePath) {
|
|
31
|
+
try {
|
|
32
|
+
await access(filePath);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function makeError(code, message, details) {
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
error: {
|
|
43
|
+
code,
|
|
44
|
+
message,
|
|
45
|
+
details,
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
function parseBounds(value) {
|
|
50
|
+
if (Array.isArray(value) && value.length === 4) {
|
|
51
|
+
return [
|
|
52
|
+
Number(value[0]) || 0,
|
|
53
|
+
Number(value[1]) || 0,
|
|
54
|
+
Number(value[2]) || 0,
|
|
55
|
+
Number(value[3]) || 0,
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
return [0, 0, 0, 0];
|
|
59
|
+
}
|
|
60
|
+
function toAxElement(raw) {
|
|
61
|
+
return {
|
|
62
|
+
id: String(raw?.id ?? ''),
|
|
63
|
+
app: String(raw?.app ?? ''),
|
|
64
|
+
pid: Number(raw?.pid ?? 0),
|
|
65
|
+
role: String(raw?.role ?? ''),
|
|
66
|
+
title: raw?.title ? String(raw.title) : undefined,
|
|
67
|
+
label: raw?.label ? String(raw.label) : undefined,
|
|
68
|
+
desc: raw?.desc ? String(raw.desc) : undefined,
|
|
69
|
+
text: raw?.text ? String(raw.text) : undefined,
|
|
70
|
+
checked: typeof raw?.checked === 'boolean' ? raw.checked : undefined,
|
|
71
|
+
selected: typeof raw?.selected === 'boolean' ? raw.selected : undefined,
|
|
72
|
+
focused: typeof raw?.focused === 'boolean' ? raw.focused : undefined,
|
|
73
|
+
actions: Array.isArray(raw?.actions) ? raw.actions.map((a) => String(a)) : undefined,
|
|
74
|
+
bounds: parseBounds(raw?.bounds),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export class AxAdapter {
|
|
78
|
+
constructor() {
|
|
79
|
+
this.helperPathCache = null;
|
|
80
|
+
this.signatures = new Map();
|
|
81
|
+
}
|
|
82
|
+
async resolveHelperPath() {
|
|
83
|
+
if (this.helperPathCache !== null) {
|
|
84
|
+
return this.helperPathCache;
|
|
85
|
+
}
|
|
86
|
+
const envOverride = process.env.DC_MACOS_AX_HELPER_PATH;
|
|
87
|
+
const archName = process.arch === 'arm64' ? 'arm64' : 'x64';
|
|
88
|
+
const candidates = [
|
|
89
|
+
envOverride,
|
|
90
|
+
path.join(PROJECT_ROOT, 'bin', 'macos', `macos-ax-helper-darwin-${archName}`),
|
|
91
|
+
path.join(PROJECT_ROOT, 'native', 'macos-ax-helper', '.build', 'apple', 'Products', 'Release', 'macos-ax-helper'),
|
|
92
|
+
path.join(PROJECT_ROOT, 'native', 'macos-ax-helper', '.build', 'release', 'macos-ax-helper'),
|
|
93
|
+
path.join(PROJECT_ROOT, 'native', 'macos-ax-helper', '.build', `${process.arch}-apple-macosx`, 'release', 'macos-ax-helper'),
|
|
94
|
+
].filter((value) => !!value);
|
|
95
|
+
for (const candidate of candidates) {
|
|
96
|
+
if (existsSync(candidate) && await isExecutable(candidate)) {
|
|
97
|
+
this.helperPathCache = candidate;
|
|
98
|
+
return candidate;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
this.helperPathCache = null;
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
async runHelper(request, timeoutMs = 15000) {
|
|
105
|
+
if (process.platform !== 'darwin') {
|
|
106
|
+
return makeError('UNSUPPORTED_PLATFORM', 'macOS control tools are only available on macOS.');
|
|
107
|
+
}
|
|
108
|
+
const helperPath = await this.resolveHelperPath();
|
|
109
|
+
if (!helperPath) {
|
|
110
|
+
return makeError('HELPER_NOT_FOUND', 'macOS accessibility helper binary not found. Build it with ./build-macos-helper.sh', { projectRoot: PROJECT_ROOT });
|
|
111
|
+
}
|
|
112
|
+
const payload = JSON.stringify(request);
|
|
113
|
+
return await new Promise((resolve) => {
|
|
114
|
+
const child = spawn(helperPath, [], {
|
|
115
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
116
|
+
});
|
|
117
|
+
let stdout = '';
|
|
118
|
+
let stderr = '';
|
|
119
|
+
let timedOut = false;
|
|
120
|
+
const timer = setTimeout(() => {
|
|
121
|
+
timedOut = true;
|
|
122
|
+
child.kill('SIGKILL');
|
|
123
|
+
}, timeoutMs);
|
|
124
|
+
child.stdout.on('data', (chunk) => {
|
|
125
|
+
stdout += chunk.toString();
|
|
126
|
+
});
|
|
127
|
+
child.stderr.on('data', (chunk) => {
|
|
128
|
+
stderr += chunk.toString();
|
|
129
|
+
});
|
|
130
|
+
child.on('error', (error) => {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
resolve(makeError('HELPER_EXEC_FAILED', `Failed to execute AX helper: ${error.message}`));
|
|
133
|
+
});
|
|
134
|
+
child.on('close', (code) => {
|
|
135
|
+
clearTimeout(timer);
|
|
136
|
+
if (timedOut) {
|
|
137
|
+
resolve(makeError('TIMEOUT', 'AX helper request timed out', { timeoutMs, command: request.command }));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const trimmed = stdout.trim();
|
|
141
|
+
if (!trimmed) {
|
|
142
|
+
resolve(makeError('HELPER_PROTOCOL_ERROR', 'AX helper returned empty response', { code, stderr }));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
let parsed;
|
|
146
|
+
try {
|
|
147
|
+
parsed = JSON.parse(trimmed);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
resolve(makeError('HELPER_PROTOCOL_ERROR', 'AX helper returned invalid JSON', { code, stderr, stdout: trimmed.slice(0, 400) }));
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (!parsed.ok) {
|
|
154
|
+
const helperCode = parsed.error?.code ?? 'INTERNAL_ERROR';
|
|
155
|
+
resolve({
|
|
156
|
+
ok: false,
|
|
157
|
+
error: {
|
|
158
|
+
code: normalizeHelperError(helperCode),
|
|
159
|
+
message: parsed.error?.message || 'AX helper request failed',
|
|
160
|
+
details: {
|
|
161
|
+
helperCode,
|
|
162
|
+
helperDetails: parsed.error?.details,
|
|
163
|
+
stderr,
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
resolve({ ok: true, data: parsed.data });
|
|
170
|
+
});
|
|
171
|
+
child.stdin.write(payload);
|
|
172
|
+
child.stdin.end();
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
rememberElements(elements) {
|
|
176
|
+
for (const element of elements) {
|
|
177
|
+
if (!element.id) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
this.signatures.set(element.id, {
|
|
181
|
+
app: element.app,
|
|
182
|
+
role: element.role,
|
|
183
|
+
title: element.title,
|
|
184
|
+
label: element.label,
|
|
185
|
+
text: element.text,
|
|
186
|
+
bounds: element.bounds,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
elementScore(candidate, signature) {
|
|
191
|
+
let score = 0;
|
|
192
|
+
if (candidate.role === signature.role) {
|
|
193
|
+
score += 8;
|
|
194
|
+
}
|
|
195
|
+
if (signature.title && candidate.title === signature.title) {
|
|
196
|
+
score += 6;
|
|
197
|
+
}
|
|
198
|
+
if (signature.label && candidate.label === signature.label) {
|
|
199
|
+
score += 4;
|
|
200
|
+
}
|
|
201
|
+
if (signature.text && candidate.text === signature.text) {
|
|
202
|
+
score += 3;
|
|
203
|
+
}
|
|
204
|
+
if (signature.bounds && candidate.bounds) {
|
|
205
|
+
const dx = Math.abs(candidate.bounds[0] - signature.bounds[0]);
|
|
206
|
+
const dy = Math.abs(candidate.bounds[1] - signature.bounds[1]);
|
|
207
|
+
if (dx < 8 && dy < 8) {
|
|
208
|
+
score += 5;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return score;
|
|
212
|
+
}
|
|
213
|
+
async fallbackRefindElement(staleId) {
|
|
214
|
+
const signature = this.signatures.get(staleId);
|
|
215
|
+
if (!signature) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const queryText = signature.title || signature.label || signature.text;
|
|
219
|
+
const refreshed = await this.listElements({
|
|
220
|
+
scope: 'app',
|
|
221
|
+
app: signature.app,
|
|
222
|
+
text: queryText,
|
|
223
|
+
role: signature.role,
|
|
224
|
+
limit: 50,
|
|
225
|
+
depth: 12,
|
|
226
|
+
});
|
|
227
|
+
if (!refreshed.ok || !refreshed.data || refreshed.data.length === 0) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const sorted = [...refreshed.data].sort((a, b) => this.elementScore(b, signature) - this.elementScore(a, signature));
|
|
231
|
+
return sorted[0] ?? null;
|
|
232
|
+
}
|
|
233
|
+
async status() {
|
|
234
|
+
const result = await this.runHelper({ command: 'status' }, 8000);
|
|
235
|
+
if (!result.ok) {
|
|
236
|
+
return result;
|
|
237
|
+
}
|
|
238
|
+
return {
|
|
239
|
+
ok: true,
|
|
240
|
+
data: {
|
|
241
|
+
...(result.data ?? { platform: process.platform, hasPermission: false }),
|
|
242
|
+
platform: process.platform,
|
|
243
|
+
helperPath: await this.resolveHelperPath() ?? undefined,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
async listApps() {
|
|
248
|
+
const result = await this.runHelper({ command: 'list_apps' });
|
|
249
|
+
if (!result.ok) {
|
|
250
|
+
return {
|
|
251
|
+
ok: false,
|
|
252
|
+
error: result.error,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const apps = Array.isArray(result.data?.apps)
|
|
256
|
+
? result.data.apps.map((app) => ({
|
|
257
|
+
name: String(app?.name ?? 'Unknown'),
|
|
258
|
+
pid: Number(app?.pid ?? 0),
|
|
259
|
+
bundleId: app?.bundleId ? String(app.bundleId) : undefined,
|
|
260
|
+
active: Boolean(app?.active),
|
|
261
|
+
}))
|
|
262
|
+
: [];
|
|
263
|
+
return { ok: true, data: apps };
|
|
264
|
+
}
|
|
265
|
+
async listElements(args) {
|
|
266
|
+
const roleFilters = expandRoleAlias(args.role);
|
|
267
|
+
const result = await this.runHelper({
|
|
268
|
+
command: 'list_elements',
|
|
269
|
+
args: {
|
|
270
|
+
scope: args.scope ?? 'top_window',
|
|
271
|
+
app: args.app,
|
|
272
|
+
text: args.text,
|
|
273
|
+
roles: roleFilters,
|
|
274
|
+
depth: args.depth,
|
|
275
|
+
limit: args.limit,
|
|
276
|
+
},
|
|
277
|
+
});
|
|
278
|
+
if (!result.ok) {
|
|
279
|
+
return {
|
|
280
|
+
ok: false,
|
|
281
|
+
error: result.error,
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const elements = Array.isArray(result.data?.elements)
|
|
285
|
+
? result.data.elements.map(toAxElement).filter((element) => element.id)
|
|
286
|
+
: [];
|
|
287
|
+
this.rememberElements(elements);
|
|
288
|
+
return { ok: true, data: elements };
|
|
289
|
+
}
|
|
290
|
+
async find(args) {
|
|
291
|
+
const index = Math.max(0, args.index ?? 0);
|
|
292
|
+
const roleFilters = expandRoleAlias(args.role);
|
|
293
|
+
const limit = Math.max(index + 1, args.limit ?? 0) || undefined;
|
|
294
|
+
const result = await this.runHelper({
|
|
295
|
+
command: 'find',
|
|
296
|
+
args: {
|
|
297
|
+
app: args.app,
|
|
298
|
+
text: args.text,
|
|
299
|
+
roles: roleFilters,
|
|
300
|
+
depth: args.depth,
|
|
301
|
+
limit,
|
|
302
|
+
index,
|
|
303
|
+
},
|
|
304
|
+
});
|
|
305
|
+
if (!result.ok) {
|
|
306
|
+
return {
|
|
307
|
+
ok: false,
|
|
308
|
+
error: result.error,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const elements = Array.isArray(result.data?.elements)
|
|
312
|
+
? result.data.elements.map(toAxElement).filter((element) => element.id)
|
|
313
|
+
: [];
|
|
314
|
+
this.rememberElements(elements);
|
|
315
|
+
return { ok: true, data: elements };
|
|
316
|
+
}
|
|
317
|
+
async clickById(id, appHint) {
|
|
318
|
+
const clickResult = await this.runHelper({
|
|
319
|
+
command: 'click',
|
|
320
|
+
args: {
|
|
321
|
+
id,
|
|
322
|
+
app: appHint,
|
|
323
|
+
},
|
|
324
|
+
});
|
|
325
|
+
if (clickResult.ok) {
|
|
326
|
+
return clickResult;
|
|
327
|
+
}
|
|
328
|
+
if (clickResult.error?.code !== 'NOT_FOUND') {
|
|
329
|
+
return clickResult;
|
|
330
|
+
}
|
|
331
|
+
const fallbackElement = await this.fallbackRefindElement(id);
|
|
332
|
+
if (!fallbackElement) {
|
|
333
|
+
return clickResult;
|
|
334
|
+
}
|
|
335
|
+
return this.runHelper({
|
|
336
|
+
command: 'click',
|
|
337
|
+
args: {
|
|
338
|
+
id: fallbackElement.id,
|
|
339
|
+
app: fallbackElement.app,
|
|
340
|
+
},
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
async typeText(text) {
|
|
344
|
+
return this.runHelper({
|
|
345
|
+
command: 'type_text',
|
|
346
|
+
args: { text },
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
async pressKey(key, modifiers = []) {
|
|
350
|
+
return this.runHelper({
|
|
351
|
+
command: 'press_key',
|
|
352
|
+
args: { key, modifiers },
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
async activate(app) {
|
|
356
|
+
return this.runHelper({
|
|
357
|
+
command: 'activate',
|
|
358
|
+
args: { app },
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
async waitFor(args) {
|
|
362
|
+
const result = await this.runHelper({
|
|
363
|
+
command: 'wait_for',
|
|
364
|
+
args: {
|
|
365
|
+
app: args.app,
|
|
366
|
+
text: args.text,
|
|
367
|
+
roles: expandRoleAlias(args.role),
|
|
368
|
+
timeout_ms: args.timeout_ms,
|
|
369
|
+
depth: args.depth,
|
|
370
|
+
},
|
|
371
|
+
}, (args.timeout_ms ?? 5000) + 3000);
|
|
372
|
+
if (!result.ok) {
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
error: result.error,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
const element = result.data?.element ? toAxElement(result.data.element) : null;
|
|
379
|
+
if (!element) {
|
|
380
|
+
return makeError('NOT_FOUND', 'Element not found before timeout');
|
|
381
|
+
}
|
|
382
|
+
this.rememberElements([element]);
|
|
383
|
+
return { ok: true, data: element };
|
|
384
|
+
}
|
|
385
|
+
async getState(args) {
|
|
386
|
+
const found = await this.find(args);
|
|
387
|
+
if (!found.ok) {
|
|
388
|
+
return {
|
|
389
|
+
ok: false,
|
|
390
|
+
error: found.error,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
const element = found.data?.[0];
|
|
394
|
+
if (!element) {
|
|
395
|
+
return makeError('NOT_FOUND', `No element matched criteria in ${args.app}`);
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
ok: true,
|
|
399
|
+
data: {
|
|
400
|
+
element,
|
|
401
|
+
checked: element.checked,
|
|
402
|
+
selected: element.selected,
|
|
403
|
+
text: element.text,
|
|
404
|
+
},
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
async scroll(args) {
|
|
408
|
+
return this.runHelper({
|
|
409
|
+
command: 'scroll',
|
|
410
|
+
args: {
|
|
411
|
+
x: args.x,
|
|
412
|
+
y: args.y,
|
|
413
|
+
direction: args.direction ?? 'down',
|
|
414
|
+
amount: args.amount ?? 3,
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
async batch(commands, stopOnError) {
|
|
419
|
+
const waitBudgetMs = commands.reduce((sum, command) => {
|
|
420
|
+
if (command.action === 'wait') {
|
|
421
|
+
return sum + Math.max(0, command.ms ?? 500);
|
|
422
|
+
}
|
|
423
|
+
if (command.action === 'wait_for') {
|
|
424
|
+
return sum + Math.max(0, command.timeout_ms ?? 5000);
|
|
425
|
+
}
|
|
426
|
+
return sum;
|
|
427
|
+
}, 0);
|
|
428
|
+
const timeoutMs = Math.min(Math.max(30000, waitBudgetMs + commands.length * 4000), 5 * 60 * 1000);
|
|
429
|
+
return this.runHelper({
|
|
430
|
+
command: 'batch',
|
|
431
|
+
args: {
|
|
432
|
+
commands,
|
|
433
|
+
stop_on_error: stopOnError,
|
|
434
|
+
},
|
|
435
|
+
}, timeoutMs);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
export const axAdapter = new AxAdapter();
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ElectronDebugAttachResult, ElectronDebugEvalResult, MacosControlResult } from './types.js';
|
|
2
|
+
export declare class CdpAdapter {
|
|
3
|
+
private sessions;
|
|
4
|
+
private fetchTargets;
|
|
5
|
+
private bindSessionHandlers;
|
|
6
|
+
private sendCommand;
|
|
7
|
+
attach(args: {
|
|
8
|
+
host?: string;
|
|
9
|
+
port?: number;
|
|
10
|
+
targetIndex?: number;
|
|
11
|
+
targetId?: string;
|
|
12
|
+
}): Promise<MacosControlResult<ElectronDebugAttachResult>>;
|
|
13
|
+
evaluate(args: {
|
|
14
|
+
sessionId: string;
|
|
15
|
+
expression: string;
|
|
16
|
+
returnByValue?: boolean;
|
|
17
|
+
awaitPromise?: boolean;
|
|
18
|
+
}): Promise<MacosControlResult<ElectronDebugEvalResult>>;
|
|
19
|
+
disconnect(sessionId: string): Promise<MacosControlResult<{
|
|
20
|
+
sessionId: string;
|
|
21
|
+
}>>;
|
|
22
|
+
}
|
|
23
|
+
export declare const cdpAdapter: CdpAdapter;
|