@hypothesi/tauri-mcp-server 0.12.0 → 0.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -18,9 +18,10 @@ export declare const InteractSchema: z.ZodObject<{
18
18
  windowId: z.ZodOptional<z.ZodString>;
19
19
  appIdentifier: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
20
20
  } & {
21
- action: z.ZodEnum<["click", "double-click", "long-press", "scroll", "swipe", "focus"]>;
21
+ action: z.ZodEnum<["click", "double-click", "long-press", "scroll", "swipe", "focus", "hover", "right-click"]>;
22
22
  selector: z.ZodOptional<z.ZodString>;
23
23
  strategy: z.ZodDefault<z.ZodEnum<["css", "xpath", "text"]>>;
24
+ nth: z.ZodOptional<z.ZodNumber>;
24
25
  x: z.ZodOptional<z.ZodNumber>;
25
26
  y: z.ZodOptional<z.ZodNumber>;
26
27
  duration: z.ZodOptional<z.ZodNumber>;
@@ -31,11 +32,12 @@ export declare const InteractSchema: z.ZodObject<{
31
32
  toX: z.ZodOptional<z.ZodNumber>;
32
33
  toY: z.ZodOptional<z.ZodNumber>;
33
34
  }, "strip", z.ZodTypeAny, {
34
- action: "swipe" | "focus" | "click" | "double-click" | "long-press" | "scroll";
35
+ action: "swipe" | "focus" | "click" | "double-click" | "long-press" | "scroll" | "hover" | "right-click";
35
36
  strategy: "css" | "text" | "xpath";
36
37
  windowId?: string | undefined;
37
38
  appIdentifier?: string | number | undefined;
38
39
  selector?: string | undefined;
40
+ nth?: number | undefined;
39
41
  x?: number | undefined;
40
42
  y?: number | undefined;
41
43
  duration?: number | undefined;
@@ -46,11 +48,12 @@ export declare const InteractSchema: z.ZodObject<{
46
48
  toX?: number | undefined;
47
49
  toY?: number | undefined;
48
50
  }, {
49
- action: "swipe" | "focus" | "click" | "double-click" | "long-press" | "scroll";
51
+ action: "swipe" | "focus" | "click" | "double-click" | "long-press" | "scroll" | "hover" | "right-click";
50
52
  windowId?: string | undefined;
51
53
  appIdentifier?: string | number | undefined;
52
54
  selector?: string | undefined;
53
55
  strategy?: "css" | "text" | "xpath" | undefined;
56
+ nth?: number | undefined;
54
57
  x?: number | undefined;
55
58
  y?: number | undefined;
56
59
  duration?: number | undefined;
@@ -69,19 +72,22 @@ export declare const ScreenshotSchema: z.ZodObject<{
69
72
  quality: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
70
73
  filePath: z.ZodOptional<z.ZodString>;
71
74
  maxWidth: z.ZodOptional<z.ZodNumber>;
75
+ allowScreenCapture: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
72
76
  }, "strip", z.ZodTypeAny, {
73
77
  format: "png" | "jpeg";
74
78
  quality: number;
79
+ allowScreenCapture: boolean;
75
80
  windowId?: string | undefined;
76
81
  appIdentifier?: string | number | undefined;
77
82
  maxWidth?: number | undefined;
78
83
  filePath?: string | undefined;
79
84
  }, {
80
- format?: "png" | "jpeg" | undefined;
81
- quality?: number | undefined;
82
85
  windowId?: string | undefined;
83
86
  appIdentifier?: string | number | undefined;
87
+ format?: "png" | "jpeg" | undefined;
88
+ quality?: number | undefined;
84
89
  maxWidth?: number | undefined;
90
+ allowScreenCapture?: boolean | undefined;
85
91
  filePath?: string | undefined;
86
92
  }>;
87
93
  export declare const KeyboardSchema: z.ZodObject<{
@@ -91,25 +97,28 @@ export declare const KeyboardSchema: z.ZodObject<{
91
97
  action: z.ZodEnum<["type", "press", "down", "up"]>;
92
98
  selector: z.ZodOptional<z.ZodString>;
93
99
  strategy: z.ZodDefault<z.ZodEnum<["css", "xpath", "text"]>>;
100
+ nth: z.ZodOptional<z.ZodNumber>;
94
101
  text: z.ZodOptional<z.ZodString>;
95
102
  key: z.ZodOptional<z.ZodString>;
96
103
  modifiers: z.ZodOptional<z.ZodArray<z.ZodEnum<["Control", "Alt", "Shift", "Meta"]>, "many">>;
97
104
  }, "strip", z.ZodTypeAny, {
98
105
  action: "type" | "press" | "down" | "up";
99
106
  strategy: "css" | "text" | "xpath";
100
- text?: string | undefined;
101
107
  windowId?: string | undefined;
102
108
  appIdentifier?: string | number | undefined;
109
+ text?: string | undefined;
103
110
  selector?: string | undefined;
111
+ nth?: number | undefined;
104
112
  key?: string | undefined;
105
113
  modifiers?: ("Control" | "Alt" | "Shift" | "Meta")[] | undefined;
106
114
  }, {
107
115
  action: "type" | "press" | "down" | "up";
108
- text?: string | undefined;
109
116
  windowId?: string | undefined;
110
117
  appIdentifier?: string | number | undefined;
118
+ text?: string | undefined;
111
119
  selector?: string | undefined;
112
120
  strategy?: "css" | "text" | "xpath" | undefined;
121
+ nth?: number | undefined;
113
122
  key?: string | undefined;
114
123
  modifiers?: ("Control" | "Alt" | "Shift" | "Meta")[] | undefined;
115
124
  }>;
@@ -136,42 +145,22 @@ export declare const WaitForSchema: z.ZodObject<{
136
145
  appIdentifier?: string | number | undefined;
137
146
  strategy?: "css" | "text" | "xpath" | undefined;
138
147
  }>;
139
- export declare const GetStylesSchema: z.ZodObject<{
140
- windowId: z.ZodOptional<z.ZodString>;
141
- appIdentifier: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
142
- } & {
143
- selector: z.ZodString;
144
- strategy: z.ZodDefault<z.ZodEnum<["css", "xpath", "text"]>>;
145
- properties: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
146
- multiple: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
147
- }, "strip", z.ZodTypeAny, {
148
- selector: string;
149
- strategy: "css" | "text" | "xpath";
150
- multiple: boolean;
151
- windowId?: string | undefined;
152
- appIdentifier?: string | number | undefined;
153
- properties?: string[] | undefined;
154
- }, {
155
- selector: string;
156
- windowId?: string | undefined;
157
- appIdentifier?: string | number | undefined;
158
- strategy?: "css" | "text" | "xpath" | undefined;
159
- properties?: string[] | undefined;
160
- multiple?: boolean | undefined;
161
- }>;
162
148
  export declare const ExecuteJavaScriptSchema: z.ZodObject<{
163
149
  windowId: z.ZodOptional<z.ZodString>;
164
150
  appIdentifier: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
165
151
  } & {
166
152
  script: z.ZodString;
167
153
  args: z.ZodOptional<z.ZodArray<z.ZodUnknown, "many">>;
154
+ timeout: z.ZodOptional<z.ZodNumber>;
168
155
  }, "strip", z.ZodTypeAny, {
169
156
  script: string;
157
+ timeout?: number | undefined;
170
158
  windowId?: string | undefined;
171
159
  appIdentifier?: string | number | undefined;
172
160
  args?: unknown[] | undefined;
173
161
  }, {
174
162
  script: string;
163
+ timeout?: number | undefined;
175
164
  windowId?: string | undefined;
176
165
  appIdentifier?: string | number | undefined;
177
166
  args?: unknown[] | undefined;
@@ -181,14 +170,20 @@ export declare const FocusElementSchema: z.ZodObject<{
181
170
  appIdentifier: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
182
171
  } & {
183
172
  selector: z.ZodString;
173
+ strategy: z.ZodDefault<z.ZodEnum<["css", "xpath", "text"]>>;
174
+ nth: z.ZodOptional<z.ZodNumber>;
184
175
  }, "strip", z.ZodTypeAny, {
185
176
  selector: string;
177
+ strategy: "css" | "text" | "xpath";
186
178
  windowId?: string | undefined;
187
179
  appIdentifier?: string | number | undefined;
180
+ nth?: number | undefined;
188
181
  }, {
189
182
  selector: string;
190
183
  windowId?: string | undefined;
191
184
  appIdentifier?: string | number | undefined;
185
+ strategy?: "css" | "text" | "xpath" | undefined;
186
+ nth?: number | undefined;
192
187
  }>;
193
188
  export declare const FindElementSchema: z.ZodObject<{
194
189
  windowId: z.ZodOptional<z.ZodString>;
@@ -196,16 +191,31 @@ export declare const FindElementSchema: z.ZodObject<{
196
191
  } & {
197
192
  selector: z.ZodString;
198
193
  strategy: z.ZodDefault<z.ZodEnum<["css", "xpath", "text"]>>;
194
+ nth: z.ZodOptional<z.ZodNumber>;
195
+ limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
196
+ properties: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
197
+ visibleOnly: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
198
+ includeHtml: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
199
199
  }, "strip", z.ZodTypeAny, {
200
200
  selector: string;
201
201
  strategy: "css" | "text" | "xpath";
202
+ limit: number;
203
+ visibleOnly: boolean;
204
+ includeHtml: boolean;
202
205
  windowId?: string | undefined;
203
206
  appIdentifier?: string | number | undefined;
207
+ nth?: number | undefined;
208
+ properties?: string[] | undefined;
204
209
  }, {
205
210
  selector: string;
206
211
  windowId?: string | undefined;
207
212
  appIdentifier?: string | number | undefined;
208
213
  strategy?: "css" | "text" | "xpath" | undefined;
214
+ nth?: number | undefined;
215
+ limit?: number | undefined;
216
+ properties?: string[] | undefined;
217
+ visibleOnly?: boolean | undefined;
218
+ includeHtml?: boolean | undefined;
209
219
  }>;
210
220
  export declare const GetConsoleLogsSchema: z.ZodObject<{
211
221
  windowId: z.ZodOptional<z.ZodString>;
@@ -215,14 +225,14 @@ export declare const GetConsoleLogsSchema: z.ZodObject<{
215
225
  since: z.ZodOptional<z.ZodString>;
216
226
  }, "strip", z.ZodTypeAny, {
217
227
  filter?: string | undefined;
228
+ since?: string | undefined;
218
229
  windowId?: string | undefined;
219
230
  appIdentifier?: string | number | undefined;
220
- since?: string | undefined;
221
231
  }, {
222
232
  filter?: string | undefined;
233
+ since?: string | undefined;
223
234
  windowId?: string | undefined;
224
235
  appIdentifier?: string | number | undefined;
225
- since?: string | undefined;
226
236
  }>;
227
237
  export declare const DomSnapshotSchema: z.ZodObject<{
228
238
  windowId: z.ZodOptional<z.ZodString>;
@@ -248,6 +258,7 @@ export declare function interact(options: {
248
258
  action: string;
249
259
  selector?: string;
250
260
  strategy?: string;
261
+ nth?: number;
251
262
  x?: number;
252
263
  y?: number;
253
264
  duration?: number;
@@ -267,6 +278,7 @@ export interface ScreenshotOptions {
267
278
  filePath?: string;
268
279
  appIdentifier?: string | number;
269
280
  maxWidth?: number;
281
+ allowScreenCapture?: boolean;
270
282
  }
271
283
  export interface ScreenshotFileResult {
272
284
  filePath: string;
@@ -277,6 +289,7 @@ export interface KeyboardOptions {
277
289
  action: string;
278
290
  selectorOrKey?: string;
279
291
  strategy?: string;
292
+ nth?: number;
280
293
  textOrModifiers?: string | string[];
281
294
  modifiers?: string[];
282
295
  windowId?: string;
@@ -292,18 +305,10 @@ export interface WaitForOptions {
292
305
  appIdentifier?: string | number;
293
306
  }
294
307
  export declare function waitFor(options: WaitForOptions): Promise<string>;
295
- export interface GetStylesOptions {
296
- selector: string;
297
- strategy?: string;
298
- properties?: string[];
299
- multiple?: boolean;
300
- windowId?: string;
301
- appIdentifier?: string | number;
302
- }
303
- export declare function getStyles(options: GetStylesOptions): Promise<string>;
304
308
  export interface ExecuteJavaScriptOptions {
305
309
  script: string;
306
310
  args?: unknown[];
311
+ timeout?: number;
307
312
  windowId?: string;
308
313
  appIdentifier?: string | number;
309
314
  }
@@ -311,6 +316,7 @@ export declare function executeJavaScript(options: ExecuteJavaScriptOptions): Pr
311
316
  export interface FocusElementOptions {
312
317
  selector: string;
313
318
  strategy?: string;
319
+ nth?: number;
314
320
  windowId?: string;
315
321
  appIdentifier?: string | number;
316
322
  }
@@ -318,6 +324,11 @@ export declare function focusElement(options: FocusElementOptions): Promise<stri
318
324
  export interface FindElementOptions {
319
325
  selector: string;
320
326
  strategy: string;
327
+ nth?: number;
328
+ limit?: number;
329
+ properties?: string[];
330
+ visibleOnly?: boolean;
331
+ includeHtml?: boolean;
321
332
  windowId?: string;
322
333
  appIdentifier?: string | number;
323
334
  }
@@ -326,6 +337,10 @@ export interface FindElementOptions {
326
337
  */
327
338
  export declare function findElement(options: FindElementOptions): Promise<string>;
328
339
  export interface GetConsoleLogsOptions {
340
+ lines?: number;
341
+ level?: 'log' | 'debug' | 'info' | 'warn' | 'error';
342
+ maxChars?: number;
343
+ maxCharsPerEntry?: number;
329
344
  filter?: string;
330
345
  since?: string;
331
346
  windowId?: string;
@@ -1,7 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { writeFile } from 'node:fs/promises';
3
3
  import { resolve } from 'node:path';
4
- import { executeInWebview, executeInWebviewWithContext, captureScreenshot, getConsoleLogs as getConsoleLogsFromCapture, } from './webview-executor.js';
4
+ import { executeInWebview, executeInWebviewWithContext, executeAsyncInWebview, captureScreenshot, getConsoleLogs as getConsoleLogsFromCapture, } from './webview-executor.js';
5
5
  import { SCRIPTS, buildScript, buildTypeScript, buildKeyEventScript } from './scripts/index.js';
6
6
  // ============================================================================
7
7
  // Base Schema for Window Targeting
@@ -24,14 +24,21 @@ export const WindowTargetSchema = z.object({
24
24
  const selectorStrategyField = z.enum(['css', 'xpath', 'text']).default('css').describe('Selector strategy: "css" (default) for CSS selectors, "xpath" for XPath expressions, ' +
25
25
  '"text" to find elements by text content, with fallback to placeholder, aria-label, ' +
26
26
  'and title attributes. Ref IDs (e.g., "ref=e3") work with any strategy.');
27
+ /**
28
+ * Optional nth parameter for disambiguating multiple matches.
29
+ * Required when a text selector matches more than one visible element.
30
+ */
31
+ const nthField = z.number().int().min(0).optional().describe('Zero-based index to pick one of several matching elements. ' +
32
+ 'Omit when the selector is unique. Required when a text selector matches more than one visible element.');
27
33
  // ============================================================================
28
34
  // Schemas
29
35
  // ============================================================================
30
36
  export const InteractSchema = WindowTargetSchema.extend({
31
- action: z.enum(['click', 'double-click', 'long-press', 'scroll', 'swipe', 'focus'])
37
+ action: z.enum(['click', 'double-click', 'long-press', 'scroll', 'swipe', 'focus', 'hover', 'right-click'])
32
38
  .describe('Type of interaction to perform'),
33
39
  selector: z.string().optional().describe('Element selector: CSS selector (default), XPath expression, text content, or ref ID (e.g., "ref=e3")'),
34
40
  strategy: selectorStrategyField,
41
+ nth: nthField,
35
42
  x: z.number().optional().describe('X coordinate for direct coordinate interaction'),
36
43
  y: z.number().optional().describe('Y coordinate for direct coordinate interaction'),
37
44
  duration: z.number().optional()
@@ -49,6 +56,7 @@ export const ScreenshotSchema = WindowTargetSchema.extend({
49
56
  filePath: z.string().optional().describe('File path to save the screenshot to instead of returning as base64'),
50
57
  maxWidth: z.number().int().positive().optional().describe('Maximum width in pixels. Images wider than this will be scaled down proportionally. ' +
51
58
  'Can also be set via TAURI_MCP_SCREENSHOT_MAX_WIDTH environment variable.'),
59
+ allowScreenCapture: z.boolean().optional().default(false).describe('Allow an interactive OS screen-sharing permission prompt if native and html2canvas capture fail'),
52
60
  });
53
61
  export const KeyboardSchema = WindowTargetSchema.extend({
54
62
  action: z.enum(['type', 'press', 'down', 'up'])
@@ -56,6 +64,7 @@ export const KeyboardSchema = WindowTargetSchema.extend({
56
64
  selector: z.string().optional().describe('Element selector for element to type into (required for "type" action): ' +
57
65
  'CSS selector (default), XPath, text content, or ref ID'),
58
66
  strategy: selectorStrategyField,
67
+ nth: nthField,
59
68
  text: z.string().optional().describe('Text to type (required for "type" action)'),
60
69
  key: z.string().optional().describe('Key to press (required for "press/down/up" actions, e.g., "Enter", "a", "Escape")'),
61
70
  modifiers: z.array(z.enum(['Control', 'Alt', 'Shift', 'Meta'])).optional().describe('Modifier keys to hold'),
@@ -66,26 +75,30 @@ export const WaitForSchema = WindowTargetSchema.extend({
66
75
  strategy: selectorStrategyField.describe('Selector strategy (applies when type is "selector"): "css" (default), "xpath", or "text".'),
67
76
  timeout: z.number().optional().default(5000).describe('Timeout in milliseconds (default: 5000ms)'),
68
77
  });
69
- export const GetStylesSchema = WindowTargetSchema.extend({
70
- selector: z.string().describe('Element selector: CSS selector (default), XPath expression, text content, or ref ID'),
71
- strategy: selectorStrategyField,
72
- properties: z.array(z.string()).optional().describe('Specific CSS properties to retrieve. If omitted, returns all computed styles'),
73
- multiple: z.boolean().optional().default(false)
74
- .describe('Whether to get styles for all matching elements (true) or just the first (false)'),
75
- });
76
78
  export const ExecuteJavaScriptSchema = WindowTargetSchema.extend({
77
79
  script: z.string().describe('JavaScript code to execute in the webview context. ' +
78
80
  'If returning a value, it must be JSON-serializable. ' +
79
81
  'For functions that return values, use IIFE syntax: "(() => { return value; })()" not "() => { return value; }"'),
80
82
  args: z.array(z.unknown()).optional().describe('Arguments to pass to the script'),
83
+ timeout: z.number().int().positive().optional().describe('Execution timeout in milliseconds'),
81
84
  });
82
85
  export const FocusElementSchema = WindowTargetSchema.extend({
83
86
  selector: z.string().describe('CSS selector for element to focus'),
87
+ strategy: selectorStrategyField,
88
+ nth: nthField,
84
89
  });
85
90
  export const FindElementSchema = WindowTargetSchema.extend({
86
91
  selector: z.string().describe('The selector to find: CSS selector (default), XPath expression, text content, or ref ID (e.g., "ref=e3"). ' +
87
92
  'Interpretation depends on strategy.'),
88
93
  strategy: selectorStrategyField,
94
+ nth: nthField,
95
+ limit: z.number().int().min(1).max(50).optional().default(5)
96
+ .describe('Maximum number of matches to return. Total match count is always reported.'),
97
+ properties: z.array(z.string()).optional().describe('Computed CSS properties to include for each match, e.g. ["display","opacity","font-size"].'),
98
+ visibleOnly: z.boolean().optional().default(true)
99
+ .describe('Return only rendered elements. Set false to include display:none and off-screen nodes.'),
100
+ includeHtml: z.boolean().optional().default(false)
101
+ .describe('Include outerHTML for each match, truncated to 2000 characters.'),
89
102
  });
90
103
  export const GetConsoleLogsSchema = WindowTargetSchema.extend({
91
104
  filter: z.string().optional().describe('Regex or keyword to filter logs'),
@@ -100,7 +113,7 @@ export const DomSnapshotSchema = WindowTargetSchema.extend({
100
113
  // Implementation Functions
101
114
  // ============================================================================
102
115
  export async function interact(options) {
103
- const { action, selector, strategy, x, y, duration, scrollX, scrollY, fromX, fromY, toX, toY, windowId, appIdentifier } = options;
116
+ const { action, selector, strategy, nth, x, y, duration, scrollX, scrollY, fromX, fromY, toX, toY, windowId, appIdentifier } = options;
104
117
  // Handle swipe action separately since it has different logic
105
118
  if (action === 'swipe') {
106
119
  return performSwipe({ fromX, fromY, toX, toY, duration, windowId, appIdentifier });
@@ -110,12 +123,13 @@ export async function interact(options) {
110
123
  if (!selector) {
111
124
  throw new Error('Focus action requires a selector');
112
125
  }
113
- return focusElement({ selector, strategy, windowId, appIdentifier });
126
+ return focusElement({ selector, strategy, nth, windowId, appIdentifier });
114
127
  }
115
128
  const script = buildScript(SCRIPTS.interact, {
116
129
  action,
117
130
  selector: selector ?? null,
118
131
  strategy: strategy ?? 'css',
132
+ nth: nth ?? null,
119
133
  x: x ?? null,
120
134
  y: y ?? null,
121
135
  duration: duration ?? 500,
@@ -145,9 +159,11 @@ async function performSwipe(options) {
145
159
  }
146
160
  }
147
161
  export async function screenshot(options = {}) {
148
- const { quality, format = 'jpeg', windowId, filePath, appIdentifier, maxWidth } = options;
162
+ const { quality, format = 'jpeg', windowId, filePath, appIdentifier, maxWidth, allowScreenCapture } = options;
149
163
  // Use the native screenshot function from webview-executor
150
- const result = await captureScreenshot({ format, quality, windowId, appIdentifier, maxWidth });
164
+ const result = await captureScreenshot({
165
+ format, quality, windowId, appIdentifier, maxWidth, allowScreenCapture,
166
+ });
151
167
  // If filePath is provided, write to file instead of returning base64
152
168
  if (filePath) {
153
169
  // Find the image content in the result
@@ -162,15 +178,15 @@ export async function screenshot(options = {}) {
162
178
  return result;
163
179
  }
164
180
  export async function keyboard(options) {
165
- const { action, selectorOrKey, strategy, textOrModifiers, modifiers, windowId, appIdentifier } = options;
181
+ const { action, selectorOrKey, strategy, nth, textOrModifiers, modifiers, windowId, appIdentifier } = options;
166
182
  // Handle the different parameter combinations based on action
167
183
  if (action === 'type') {
168
184
  const selector = selectorOrKey;
169
185
  const text = textOrModifiers;
170
- if (!selector || !text) {
186
+ if (!selector || text === undefined) {
171
187
  throw new Error('Type action requires both selector and text parameters');
172
188
  }
173
- const script = buildTypeScript(selector, text, strategy);
189
+ const script = buildTypeScript(selector, text, strategy, nth);
174
190
  try {
175
191
  return await executeInWebview(script, windowId, appIdentifier);
176
192
  }
@@ -205,24 +221,8 @@ export async function waitFor(options) {
205
221
  throw new Error(`Wait failed: ${message}`);
206
222
  }
207
223
  }
208
- export async function getStyles(options) {
209
- const { selector, strategy, properties, multiple = false, windowId, appIdentifier } = options;
210
- const script = buildScript(SCRIPTS.getStyles, {
211
- selector,
212
- strategy: strategy ?? 'css',
213
- properties: properties || [],
214
- multiple,
215
- });
216
- try {
217
- return await executeInWebview(script, windowId, appIdentifier);
218
- }
219
- catch (error) {
220
- const message = error instanceof Error ? error.message : String(error);
221
- throw new Error(`Get styles failed: ${message}`);
222
- }
223
- }
224
224
  export async function executeJavaScript(options) {
225
- const { script, args, windowId, appIdentifier } = options;
225
+ const { script, args, timeout, windowId, appIdentifier } = options;
226
226
  // If args are provided, we need to inject them into the script context
227
227
  const wrappedScript = args && args.length > 0
228
228
  ? `
@@ -233,7 +233,13 @@ export async function executeJavaScript(options) {
233
233
  `
234
234
  : script;
235
235
  try {
236
- const { result, windowLabel, warning } = await executeInWebviewWithContext(wrappedScript, windowId, appIdentifier);
236
+ const execution = timeout === undefined
237
+ ? await executeInWebviewWithContext(wrappedScript, windowId, appIdentifier)
238
+ : {
239
+ result: await executeAsyncInWebview(buildTimedScript(wrappedScript), windowId, timeout, appIdentifier),
240
+ windowLabel: windowId ?? 'main',
241
+ };
242
+ const { result, windowLabel, warning } = execution;
237
243
  // Build response with window context
238
244
  let response = result;
239
245
  if (warning) {
@@ -248,9 +254,16 @@ export async function executeJavaScript(options) {
248
254
  throw new Error(`JavaScript execution failed: ${message}`);
249
255
  }
250
256
  }
257
+ function buildTimedScript(script) {
258
+ const trimmed = script.trim();
259
+ if (/\breturn\b/.test(trimmed) && !trimmed.startsWith('(')) {
260
+ return `return (async () => { ${trimmed} })();`;
261
+ }
262
+ return `return (${trimmed});`;
263
+ }
251
264
  export async function focusElement(options) {
252
- const { selector, strategy, windowId, appIdentifier } = options;
253
- const script = buildScript(SCRIPTS.focus, { selector, strategy: strategy ?? 'css' });
265
+ const { selector, strategy, nth, windowId, appIdentifier } = options;
266
+ const script = buildScript(SCRIPTS.focus, { selector, strategy: strategy ?? 'css', nth: nth ?? null });
254
267
  try {
255
268
  return await executeInWebview(script, windowId, appIdentifier);
256
269
  }
@@ -263,8 +276,17 @@ export async function focusElement(options) {
263
276
  * Find an element using various selector strategies.
264
277
  */
265
278
  export async function findElement(options) {
266
- const { selector, strategy, windowId, appIdentifier } = options;
267
- const script = buildScript(SCRIPTS.findElement, { selector, strategy });
279
+ const { selector, strategy, nth, limit, properties, visibleOnly, includeHtml, windowId, appIdentifier } = options;
280
+ await ensureAriaApiLoaded(windowId, appIdentifier);
281
+ const script = buildScript(SCRIPTS.findElement, {
282
+ selector,
283
+ strategy,
284
+ nth: nth ?? null,
285
+ limit: limit ?? 5,
286
+ properties: properties || [],
287
+ visibleOnly: visibleOnly ?? true,
288
+ includeHtml: includeHtml ?? false,
289
+ });
268
290
  try {
269
291
  return await executeInWebview(script, windowId, appIdentifier);
270
292
  }
@@ -277,9 +299,9 @@ export async function findElement(options) {
277
299
  * Get console logs from the webview.
278
300
  */
279
301
  export async function getConsoleLogs(options = {}) {
280
- const { filter, since, windowId, appIdentifier } = options;
302
+ const { lines = 50, level, maxChars = 20000, maxCharsPerEntry = 2000, filter, since, windowId, appIdentifier } = options;
281
303
  try {
282
- return await getConsoleLogsFromCapture(filter, since, windowId, appIdentifier);
304
+ return await getConsoleLogsFromCapture({ filter, since, lines, windowId, appIdentifier, level, maxChars, maxCharsPerEntry });
283
305
  }
284
306
  catch (error) {
285
307
  const message = error instanceof Error ? error.message : String(error);
@@ -2,28 +2,40 @@ import { z } from 'zod';
2
2
  export declare const ReadLogsSchema: z.ZodObject<{
3
3
  source: z.ZodEnum<["console", "android", "ios", "system"]>;
4
4
  lines: z.ZodDefault<z.ZodNumber>;
5
+ level: z.ZodOptional<z.ZodEnum<["log", "debug", "info", "warn", "error"]>>;
6
+ maxChars: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
7
+ maxCharsPerEntry: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
5
8
  filter: z.ZodOptional<z.ZodString>;
6
9
  since: z.ZodOptional<z.ZodString>;
7
10
  windowId: z.ZodOptional<z.ZodString>;
8
11
  appIdentifier: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber]>>;
9
12
  }, "strip", z.ZodTypeAny, {
10
13
  lines: number;
14
+ maxChars: number;
15
+ maxCharsPerEntry: number;
11
16
  source: "android" | "ios" | "console" | "system";
12
17
  filter?: string | undefined;
18
+ since?: string | undefined;
13
19
  windowId?: string | undefined;
14
20
  appIdentifier?: string | number | undefined;
15
- since?: string | undefined;
21
+ level?: "error" | "log" | "debug" | "info" | "warn" | undefined;
16
22
  }, {
17
23
  source: "android" | "ios" | "console" | "system";
18
24
  filter?: string | undefined;
19
25
  lines?: number | undefined;
26
+ since?: string | undefined;
20
27
  windowId?: string | undefined;
21
28
  appIdentifier?: string | number | undefined;
22
- since?: string | undefined;
29
+ level?: "error" | "log" | "debug" | "info" | "warn" | undefined;
30
+ maxChars?: number | undefined;
31
+ maxCharsPerEntry?: number | undefined;
23
32
  }>;
24
33
  export interface ReadLogsOptions {
25
34
  source: 'console' | 'android' | 'ios' | 'system';
26
35
  lines?: number;
36
+ level?: 'log' | 'debug' | 'info' | 'warn' | 'error';
37
+ maxChars?: number;
38
+ maxCharsPerEntry?: number;
27
39
  filter?: string;
28
40
  since?: string;
29
41
  windowId?: string;
@@ -47,19 +47,25 @@ function findAdbPath() {
47
47
  export const ReadLogsSchema = z.object({
48
48
  source: z.enum(['console', 'android', 'ios', 'system'])
49
49
  .describe('Log source: "console" for webview JS logs, "android" for logcat, "ios" for simulator, "system" for desktop'),
50
- lines: z.number().default(50),
50
+ lines: z.number().int().positive().default(50),
51
+ level: z.enum(['log', 'debug', 'info', 'warn', 'error']).optional()
52
+ .describe('Only return entries at this console level.'),
53
+ maxChars: z.number().int().min(500).max(100000).optional().default(20000)
54
+ .describe('Maximum characters in the response. Entries are truncated and the oldest are dropped to fit.'),
55
+ maxCharsPerEntry: z.number().int().min(100).max(20000).optional().default(2000)
56
+ .describe('Maximum characters kept from each log entry.'),
51
57
  filter: z.string().optional().describe('Regex or keyword to filter logs'),
52
58
  since: z.string().optional().describe('ISO timestamp to filter logs since (e.g. 2023-10-27T10:00:00Z)'),
53
59
  windowId: z.string().optional().describe('Window label for console logs (defaults to "main")'),
54
60
  appIdentifier: z.union([z.string(), z.number()]).optional().describe('App port or bundle ID for console logs. Defaults to the only connected app or the default app if multiple are connected.'),
55
61
  });
56
62
  export async function readLogs(options) {
57
- const { source, lines = 50, filter, since, windowId, appIdentifier } = options;
63
+ const { source, lines = 50, level, maxChars = 20000, maxCharsPerEntry = 2000, filter, since, windowId, appIdentifier } = options;
58
64
  try {
59
65
  let output = '';
60
66
  // Handle console logs (webview JS logs)
61
67
  if (source === 'console') {
62
- return await getConsoleLogs({ filter, since, windowId, appIdentifier });
68
+ return await getConsoleLogs({ lines, level, maxChars, maxCharsPerEntry, filter, since, windowId, appIdentifier });
63
69
  }
64
70
  if (source === 'android') {
65
71
  const adbPath = findAdbPath();
@@ -134,16 +140,42 @@ export async function readLogs(options) {
134
140
  return `Error reading system logs: ${e}`;
135
141
  }
136
142
  }
143
+ // Apply level filter
144
+ if (level) {
145
+ const levelPrefix = `[ ${level.toUpperCase()} ]`;
146
+ output = output.split('\n').filter((line) => { return line.includes(levelPrefix); }).join('\n');
147
+ }
148
+ // Apply regex filter
137
149
  if (filter) {
138
150
  try {
139
151
  const regex = new RegExp(filter, 'i');
140
- return output.split('\n').filter((line) => { return regex.test(line); }).join('\n');
152
+ output = output.split('\n').filter((line) => { return regex.test(line); }).join('\n');
141
153
  }
142
154
  catch (e) {
143
155
  return `Invalid filter regex: ${e}`;
144
156
  }
145
157
  }
146
- return output;
158
+ // Apply character budget
159
+ const outputLines = output.split('\n');
160
+ let budget = maxChars, dropped = 0;
161
+ const finalLines = [];
162
+ for (let i = outputLines.length - 1; i >= 0; i--) {
163
+ let line = outputLines[i];
164
+ if (line.length > maxCharsPerEntry) {
165
+ line = line.slice(0, maxCharsPerEntry) + '…[' + (line.length - maxCharsPerEntry) + ' more chars]';
166
+ }
167
+ if (line.length > budget) {
168
+ dropped = i + 1;
169
+ break;
170
+ }
171
+ budget -= line.length + 1; // +1 for newline
172
+ finalLines.unshift(line);
173
+ }
174
+ if (dropped > 0) {
175
+ finalLines.unshift('[ ' + dropped + ' older entries dropped to fit maxChars=' + maxChars +
176
+ '. Narrow with level, filter, or since. ]');
177
+ }
178
+ return finalLines.join('\n');
147
179
  }
148
180
  catch (error) {
149
181
  return `Error reading logs: ${error}`;