agentremote-cli 1.0.0__py3-none-any.whl

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.
remotecli/cli.py ADDED
@@ -0,0 +1,635 @@
1
+ """
2
+ remotecli — AgentRemote 命令行工具.
3
+
4
+ Agent 通过此 CLI 与 Middleware 交互:
5
+ remotecli cua click --pid 17376 --window-id 7014188 --target 保存
6
+ remotecli rpa run --flow crm-transaction-filter --param START_DATE=20260701 --param END_DATE=20260701
7
+ remotecli result <trace_id>
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import argparse
13
+ import json
14
+ import sys
15
+ from pathlib import Path
16
+ from typing import Any, Dict
17
+
18
+ _RUNTIME_ROOT = Path(__file__).resolve().parents[1]
19
+ _SHARED_ROOT = _RUNTIME_ROOT / "shared"
20
+ if str(_SHARED_ROOT) not in sys.path:
21
+ sys.path.insert(0, str(_SHARED_ROOT))
22
+
23
+ from agentremote_common.config import load_cli_defaults
24
+
25
+ from .client import RemoteCliClient
26
+ from .cua import CuaCommands
27
+ from .agent import AgentCommands
28
+ from .rpa import RpaCommands
29
+ from .shell import ShellCommands
30
+
31
+
32
+ def _configure_stdio() -> None:
33
+ for stream_name in ("stdout", "stderr"):
34
+ stream = getattr(sys, stream_name, None)
35
+ if hasattr(stream, "reconfigure"):
36
+ try:
37
+ stream.reconfigure(encoding="utf-8", errors="replace")
38
+ except Exception:
39
+ pass
40
+
41
+
42
+ def _print_json(data: Dict[str, Any]) -> None:
43
+ text = json.dumps(data, ensure_ascii=False, indent=2)
44
+ try:
45
+ print(text)
46
+ except UnicodeEncodeError:
47
+ print(json.dumps(data, ensure_ascii=True, indent=2))
48
+
49
+
50
+ def _maybe_wait_for_result(
51
+ client: RemoteCliClient,
52
+ submitted: Dict[str, Any],
53
+ wait: bool,
54
+ timeout_sec: float,
55
+ interval: float,
56
+ ) -> Dict[str, Any]:
57
+ if not wait:
58
+ return submitted
59
+ trace_id = submitted.get("trace_id")
60
+ if not trace_id:
61
+ return submitted
62
+ return client.poll_result(trace_id, timeout_sec=timeout_sec, interval=interval)
63
+
64
+
65
+ def _split_targets(value: str) -> list[str]:
66
+ return [part.strip() for part in value.split(",") if part.strip()]
67
+
68
+
69
+ def _parse_param_flags(values: list[str]) -> Dict[str, str]:
70
+ params: Dict[str, str] = {}
71
+ for value in values:
72
+ if "=" not in value:
73
+ raise ValueError(f"RPA --param must be KEY=VALUE: {value}")
74
+ key, param_value = value.split("=", 1)
75
+ key = key.strip()
76
+ if not key:
77
+ raise ValueError(f"RPA --param key cannot be empty: {value}")
78
+ params[key] = param_value
79
+ return params
80
+
81
+
82
+ def _add_agent_window_cache_args(parser: argparse.ArgumentParser) -> None:
83
+ parser.add_argument("--session", default="",
84
+ help="Task session id for reusing cached app pid/window_id")
85
+ parser.add_argument("--refresh-window", action="store_true",
86
+ help="Ignore cached pid/window_id and resolve the app window again")
87
+ parser.add_argument("--no-window-cache", action="store_true",
88
+ help="Disable session window cache for this command")
89
+
90
+
91
+ def main() -> None:
92
+ _configure_stdio()
93
+ pre_parser = argparse.ArgumentParser(add_help=False)
94
+ pre_parser.add_argument("--config", default="")
95
+ pre_args, _ = pre_parser.parse_known_args()
96
+ defaults = load_cli_defaults(pre_args.config)
97
+
98
+ parser = argparse.ArgumentParser(
99
+ description="AgentRemote CLI — capability-first remote control",
100
+ prog="remotecli",
101
+ )
102
+ parser.add_argument("--config", default=pre_args.config,
103
+ help="AgentRemote CLI TOML config path")
104
+ parser.add_argument("--middleware", default=defaults.middleware_url,
105
+ help="Middleware HTTP URL")
106
+ parser.add_argument("--tenant", default=defaults.tenant, help="Tenant ID")
107
+ parser.add_argument("--node", default=defaults.node, help="Node ID")
108
+ parser.add_argument("--agent", default=defaults.agent, help="Agent ID")
109
+ parser.add_argument("--handle", default=defaults.handle, help="Auth broker handle (wch_v1_...)")
110
+ parser.add_argument("--wait", dest="wait_result", action="store_true",
111
+ help="Wait for submitted task result before printing")
112
+ parser.add_argument("--wait-timeout", type=float, default=30.0,
113
+ help="Seconds to wait for task result when --wait is set")
114
+ parser.add_argument("--poll-interval", type=float, default=0.1,
115
+ help="Seconds between result polls when --wait is set")
116
+
117
+ sub = parser.add_subparsers(dest="command", help="Subcommands")
118
+
119
+ # ── cua ──────────────────────────────
120
+ cua_parser = sub.add_parser("cua", help="CUA desktop control")
121
+ cua_sub = cua_parser.add_subparsers(dest="cua_action", help="CUA actions")
122
+
123
+ # cua click
124
+ p = cua_sub.add_parser("click", help="Click an element")
125
+ p.add_argument("--pid", type=int, required=True)
126
+ p.add_argument("--window-id", type=int, required=True)
127
+ p.add_argument("--target", default="")
128
+ p.add_argument("--x", type=int, default=0)
129
+ p.add_argument("--y", type=int, default=0)
130
+ p.add_argument("--element-index", type=int, default=0)
131
+ p.add_argument("--locate-mode", choices=["smart", "uia", "som"], default="",
132
+ help="Target resolution mode: smart=UIA then SOM/OCR, uia=UIA only, som=SOM/OCR only")
133
+
134
+ # cua double-click
135
+ p = cua_sub.add_parser("double-click", help="Double-click an element")
136
+ p.add_argument("--pid", type=int, required=True)
137
+ p.add_argument("--window-id", type=int, required=True)
138
+ p.add_argument("--target", default="")
139
+ p.add_argument("--locate-mode", choices=["smart", "uia", "som"], default="",
140
+ help="Target resolution mode")
141
+
142
+ # cua right-click
143
+ p = cua_sub.add_parser("right-click", help="Right-click an element")
144
+ p.add_argument("--pid", type=int, required=True)
145
+ p.add_argument("--window-id", type=int, required=True)
146
+ p.add_argument("--target", default="")
147
+ p.add_argument("--locate-mode", choices=["smart", "uia", "som"], default="",
148
+ help="Target resolution mode")
149
+
150
+ # cua type-text
151
+ p = cua_sub.add_parser("type-text", help="Type text into an element")
152
+ p.add_argument("--pid", type=int, required=True)
153
+ p.add_argument("--window-id", type=int, default=0)
154
+ p.add_argument("--target", required=True)
155
+ p.add_argument("--text", required=True)
156
+ p.add_argument("--locate-mode", choices=["smart", "uia", "som"], default="",
157
+ help="Target resolution mode")
158
+
159
+ # cua hotkey
160
+ p = cua_sub.add_parser("hotkey", help="Send hotkey combination")
161
+ p.add_argument("--pid", type=int, required=True)
162
+ p.add_argument("--window-id", type=int, default=0)
163
+ p.add_argument("--keys", required=True, help="e.g. ctrl,s")
164
+
165
+ # cua press-key
166
+ p = cua_sub.add_parser("press-key", help="Press a single key")
167
+ p.add_argument("--pid", type=int, required=True)
168
+ p.add_argument("--window-id", type=int, default=0)
169
+ p.add_argument("--key", required=True, help="e.g. enter, tab")
170
+
171
+ # cua launch-app
172
+ p = cua_sub.add_parser("launch-app", help="Launch an application")
173
+ p.add_argument("--name", default="")
174
+ p.add_argument("--path", dest="launch_path", default="")
175
+ p.add_argument("--args", dest="app_args", default="")
176
+ p.add_argument("--wait", dest="app_wait", action="store_true",
177
+ help="Wait for launched app window before returning")
178
+
179
+ # cua list-apps
180
+ p = cua_sub.add_parser("list-apps", help="List running applications")
181
+ p.add_argument("--query", default="")
182
+ p.add_argument("--max-results", type=int, default=0)
183
+
184
+ # cua list-windows
185
+ p = cua_sub.add_parser("list-windows", help="List windows")
186
+ p.add_argument("--pid", type=int, default=0)
187
+
188
+ # cua bring-to-front
189
+ p = cua_sub.add_parser("bring-to-front", help="Bring window to front")
190
+ p.add_argument("--pid", type=int, required=True)
191
+ p.add_argument("--window-id", type=int, required=True)
192
+
193
+ # cua get-window-state
194
+ p = cua_sub.add_parser("get-window-state", help="Get window state")
195
+ p.add_argument("--pid", type=int, required=True)
196
+ p.add_argument("--window-id", type=int, required=True)
197
+ p.add_argument("--mode", choices=["uia", "vision", "som-full"], default="vision",
198
+ help="uia=UIA tree, vision=screenshot only, som-full=full YOLO/OCR annotated snapshot")
199
+
200
+ # cua probe-window
201
+ p = cua_sub.add_parser("probe-window", help="Probe target labels in one window")
202
+ p.add_argument("--pid", type=int, required=True)
203
+ p.add_argument("--window-id", type=int, required=True)
204
+ p.add_argument("--targets", required=True,
205
+ help="Comma-separated target labels to probe")
206
+ p.add_argument("--mode", choices=["smart", "uia", "som"], default="smart",
207
+ help="smart=UIA then SOM/OCR, uia=UIA only, som=SOM/OCR only")
208
+
209
+ # cua get-cursor-position
210
+ cua_sub.add_parser("get-cursor-position", help="Get current cursor position")
211
+
212
+ # cua move-mouse
213
+ p = cua_sub.add_parser("move-mouse", help="Move mouse to absolute screen coordinates")
214
+ p.add_argument("--x", type=int, required=True)
215
+ p.add_argument("--y", type=int, required=True)
216
+
217
+ # cua move-mouse-relative
218
+ p = cua_sub.add_parser("move-mouse-relative", help="Move mouse relative to current position")
219
+ p.add_argument("--dx", type=int, required=True)
220
+ p.add_argument("--dy", type=int, required=True)
221
+
222
+ # cua scroll
223
+ p = cua_sub.add_parser("scroll", help="Scroll in a window")
224
+ p.add_argument("--pid", type=int, required=True)
225
+ p.add_argument("--window-id", type=int, required=True)
226
+ p.add_argument("--dx", type=int, default=0)
227
+ p.add_argument("--dy", type=int, default=0)
228
+ p.add_argument("--direction", choices=["up", "down", "left", "right"], default="",
229
+ help="Scroll direction; preferred over dx/dy")
230
+ p.add_argument("--amount", type=int, default=0,
231
+ help="Number of scroll ticks when --direction is used")
232
+ p.add_argument("--by", choices=["line", "page"], default="",
233
+ help="Scroll granularity when --direction is used")
234
+ p.add_argument("--dispatch", choices=["background", "foreground", "auto"], default="",
235
+ help="Driver dispatch mode")
236
+
237
+ # cua drag
238
+ p = cua_sub.add_parser("drag", help="Drag from one point to another")
239
+ p.add_argument("--pid", type=int, required=True)
240
+ p.add_argument("--window-id", type=int, required=True)
241
+ p.add_argument("--from-x", type=int, default=0)
242
+ p.add_argument("--from-y", type=int, default=0)
243
+ p.add_argument("--to-x", type=int, required=True)
244
+ p.add_argument("--to-y", type=int, required=True)
245
+
246
+ # cua set-value
247
+ p = cua_sub.add_parser("set-value", help="Set value of a UI element")
248
+ p.add_argument("--pid", type=int, required=True)
249
+ p.add_argument("--window-id", type=int, default=0)
250
+ p.add_argument("--target", default="")
251
+ p.add_argument("--element-index", type=int, default=0)
252
+ p.add_argument("--value", required=True)
253
+ p.add_argument("--locate-mode", choices=["smart", "uia", "som"], default="",
254
+ help="Target resolution mode")
255
+
256
+ # ── rpa ──────────────────────────────
257
+ rpa_parser = sub.add_parser("rpa", help="RPA flow execution")
258
+ rpa_sub = rpa_parser.add_subparsers(dest="rpa_action", help="RPA actions")
259
+
260
+ p = rpa_sub.add_parser("run", help="Run an RPA flow")
261
+ p.add_argument("--flow", required=True, help="Flow ID")
262
+ p.add_argument("--task", default="", help="Task name")
263
+ p.add_argument("--param", action="append", default=[],
264
+ help="Robot variable in KEY=VALUE form; can be repeated")
265
+
266
+ p = rpa_sub.add_parser("list-flows", help="List available RPA flows")
267
+
268
+ # ── shell ────────────────────────────
269
+ shell_parser = sub.add_parser("shell", help="Remote shell execution")
270
+ shell_sub = shell_parser.add_subparsers(dest="shell_action", help="Shell actions")
271
+
272
+ # shell exec
273
+ p = shell_sub.add_parser("exec", help="Execute a shell command")
274
+ p.add_argument("--cmd", required=True, help="Command to execute")
275
+ p.add_argument("--cwd", default="", help="Working directory")
276
+ p.add_argument("--timeout", type=int, default=60, help="Timeout in seconds")
277
+
278
+ # shell exec-script
279
+ p = shell_sub.add_parser("exec-script", help="Execute a multi-line script")
280
+ p.add_argument("--script", required=True, help="Script content")
281
+ p.add_argument("--shell", default="", help="Shell type: cmd / powershell / bash / sh")
282
+ p.add_argument("--cwd", default="", help="Working directory")
283
+ p.add_argument("--timeout", type=int, default=60, help="Timeout in seconds")
284
+
285
+ # shell info
286
+ shell_sub.add_parser("info", help="Get shell/OS environment info")
287
+
288
+ # ── agent ────────────────────────────
289
+ agent_parser = sub.add_parser("agent", help="Agent-friendly high-level wrappers")
290
+ agent_sub = agent_parser.add_subparsers(dest="agent_action", help="Agent actions")
291
+
292
+ p = agent_sub.add_parser("ps", help="Run PowerShell and print unpacked result")
293
+ p.add_argument("script", help="PowerShell script")
294
+ p.add_argument("--cwd", default="", help="Working directory")
295
+ p.add_argument("--timeout", type=int, default=60, help="Remote command timeout in seconds")
296
+
297
+ p = agent_sub.add_parser("cmd", help="Run cmd.exe command and print unpacked result")
298
+ p.add_argument("cmd", help="Command to execute")
299
+ p.add_argument("--cwd", default="", help="Working directory")
300
+ p.add_argument("--timeout", type=int, default=60, help="Remote command timeout in seconds")
301
+
302
+ p = agent_sub.add_parser("open", help="Ensure an app main window exists and cache pid/window_id")
303
+ p.add_argument("app", help="Application query, e.g. firefox or 微信")
304
+ p.add_argument("--path", dest="launch_path", default="",
305
+ help="Optional explicit executable path")
306
+ p.add_argument("--args", dest="app_args", default="",
307
+ help="Optional launch arguments")
308
+ p.add_argument("--timeout", dest="launch_timeout", type=int, default=15,
309
+ help="Seconds to wait for a main window after launch")
310
+ p.add_argument("--no-start", action="store_true",
311
+ help="Only find an existing main window; do not launch")
312
+ p.add_argument("--no-foreground", action="store_true",
313
+ help="Do not try to bring the main window to front")
314
+ _add_agent_window_cache_args(p)
315
+
316
+ p = agent_sub.add_parser("observe", help="Find an app window and return its state")
317
+ p.add_argument("app", help="Application query, e.g. firefox")
318
+ p.add_argument("--mode", choices=["uia", "vision", "som-full"], default="uia",
319
+ help="uia=UIA tree, vision=screenshot only, som-full=full YOLO/OCR annotated snapshot")
320
+ p.add_argument("--format", dest="observe_format", choices=["compact", "full"], default="compact",
321
+ help="Return compact AI-facing state or full raw state")
322
+ p.add_argument("--element-limit", type=int, default=20,
323
+ help="Maximum compact elements to return")
324
+ p.add_argument("--targets", default="",
325
+ help="Comma-separated target labels to prioritize in compact output")
326
+ _add_agent_window_cache_args(p)
327
+
328
+ p = agent_sub.add_parser("probe", help="Find an app window and probe target labels")
329
+ p.add_argument("app", help="Application query, e.g. firefox")
330
+ p.add_argument("--targets", required=True,
331
+ help="Comma-separated target labels to probe")
332
+ p.add_argument("--mode", choices=["smart", "uia", "som"], default="smart",
333
+ help="smart=UIA then SOM/OCR, uia=UIA only, som=SOM/OCR only")
334
+ _add_agent_window_cache_args(p)
335
+
336
+ p = agent_sub.add_parser("click", help="Find an app window and click target text")
337
+ p.add_argument("app", help="Application query, e.g. firefox")
338
+ p.add_argument("target", help="Target text")
339
+ p.add_argument("--locate-mode", choices=["smart", "uia", "som"], default="smart",
340
+ help="Target resolution mode: smart=UIA then SOM/OCR, uia=UIA only, som=SOM/OCR only")
341
+ _add_agent_window_cache_args(p)
342
+
343
+ p = agent_sub.add_parser("type", help="Find an app window and type text into target")
344
+ p.add_argument("app", help="Application query, e.g. firefox")
345
+ p.add_argument("target", help="Target text")
346
+ p.add_argument("text", help="Text to type")
347
+ p.add_argument("--locate-mode", choices=["smart", "uia", "som"], default="smart",
348
+ help="Target resolution mode: smart=UIA then SOM/OCR, uia=UIA only, som=SOM/OCR only")
349
+ _add_agent_window_cache_args(p)
350
+
351
+ p = agent_sub.add_parser("key", help="Find an app window and press one key")
352
+ p.add_argument("app", help="Application query, e.g. firefox")
353
+ p.add_argument("key", help="Key name, e.g. escape")
354
+ _add_agent_window_cache_args(p)
355
+
356
+ p = agent_sub.add_parser("hotkey", help="Find an app window and send a hotkey")
357
+ p.add_argument("app", help="Application query, e.g. firefox")
358
+ p.add_argument("keys", help="Keys, e.g. ctrl,l or ctrl+shift+s")
359
+ _add_agent_window_cache_args(p)
360
+
361
+ p = agent_sub.add_parser("scroll", help="Find an app window and scroll it")
362
+ p.add_argument("app", help="Application query, e.g. firefox")
363
+ p.add_argument("--direction", choices=["up", "down", "left", "right"], default="down")
364
+ p.add_argument("--amount", type=int, default=3)
365
+ p.add_argument("--by", choices=["line", "page"], default="line")
366
+ p.add_argument("--dispatch", choices=["background", "foreground", "auto"], default="")
367
+ _add_agent_window_cache_args(p)
368
+
369
+ p = agent_sub.add_parser("session-clear", help="Clear cached agent window refs")
370
+ p.add_argument("session", nargs="?", default="",
371
+ help="Task session id; falls back to REMOTECLI_SESSION")
372
+
373
+ # ── result ───────────────────────────
374
+ p = sub.add_parser("result", help="Query task result")
375
+ p.add_argument("trace_id", help="Trace ID")
376
+
377
+ args = parser.parse_args()
378
+
379
+ client = RemoteCliClient(
380
+ middleware_url=args.middleware,
381
+ tenant_id=args.tenant,
382
+ node_id=args.node,
383
+ agent_id=args.agent,
384
+ handle=args.handle,
385
+ )
386
+
387
+ if args.command == "cua":
388
+ cua = CuaCommands(client)
389
+ action = args.cua_action
390
+ if action == "click":
391
+ result = cua.click(
392
+ args.pid,
393
+ args.window_id,
394
+ args.target,
395
+ args.x,
396
+ args.y,
397
+ args.element_index,
398
+ locate_mode=args.locate_mode,
399
+ )
400
+ elif action == "double-click":
401
+ result = cua.double_click(args.pid, args.window_id, args.target, locate_mode=args.locate_mode)
402
+ elif action == "right-click":
403
+ result = cua.right_click(args.pid, args.window_id, args.target, locate_mode=args.locate_mode)
404
+ elif action == "type-text":
405
+ result = cua.type_text(
406
+ args.pid,
407
+ args.target,
408
+ args.text,
409
+ window_id=args.window_id,
410
+ locate_mode=args.locate_mode,
411
+ )
412
+ elif action == "hotkey":
413
+ result = cua.hotkey(args.pid, args.keys, window_id=args.window_id)
414
+ elif action == "press-key":
415
+ result = cua.press_key(args.pid, args.key, window_id=args.window_id)
416
+ elif action == "launch-app":
417
+ result = cua.launch_app(args.name, args.launch_path, args.app_args, args.app_wait)
418
+ elif action == "list-apps":
419
+ result = cua.list_apps(args.query, args.max_results)
420
+ elif action == "list-windows":
421
+ result = cua.list_windows(args.pid)
422
+ elif action == "bring-to-front":
423
+ result = cua.bring_to_front(args.pid, args.window_id)
424
+ elif action == "get-window-state":
425
+ result = cua.get_window_state(args.pid, args.window_id, args.mode)
426
+ elif action == "probe-window":
427
+ result = cua.probe_window(args.pid, args.window_id, _split_targets(args.targets), mode=args.mode)
428
+ elif action == "get-cursor-position":
429
+ result = cua.get_cursor_position()
430
+ elif action == "move-mouse":
431
+ result = cua.move_mouse(args.x, args.y)
432
+ elif action == "move-mouse-relative":
433
+ result = cua.move_mouse_relative(args.dx, args.dy)
434
+ elif action == "scroll":
435
+ result = cua.scroll(
436
+ args.pid,
437
+ args.window_id,
438
+ args.dx,
439
+ args.dy,
440
+ direction=args.direction,
441
+ amount=args.amount,
442
+ by=args.by,
443
+ dispatch=args.dispatch,
444
+ )
445
+ elif action == "drag":
446
+ result = cua.drag(args.pid, args.window_id, args.from_x, args.from_y, args.to_x, args.to_y)
447
+ elif action == "set-value":
448
+ result = cua.set_value(
449
+ args.pid,
450
+ args.target,
451
+ args.value,
452
+ args.element_index,
453
+ args.window_id,
454
+ locate_mode=args.locate_mode,
455
+ )
456
+ else:
457
+ print(f"Unknown cua action: {action}", file=sys.stderr)
458
+ sys.exit(1)
459
+ _print_json(_maybe_wait_for_result(
460
+ client, result, args.wait_result, args.wait_timeout, args.poll_interval
461
+ ))
462
+
463
+ elif args.command == "rpa":
464
+ rpa = RpaCommands(client)
465
+ action = args.rpa_action
466
+ if action == "run":
467
+ result = rpa.run(args.flow, args.task, params=_parse_param_flags(args.param))
468
+ elif action == "list-flows":
469
+ result = rpa.list_flows()
470
+ else:
471
+ print(f"Unknown rpa action: {action}", file=sys.stderr)
472
+ sys.exit(1)
473
+ _print_json(_maybe_wait_for_result(
474
+ client, result, args.wait_result, args.wait_timeout, args.poll_interval
475
+ ))
476
+
477
+ elif args.command == "shell":
478
+ shell = ShellCommands(client)
479
+ action = args.shell_action
480
+ if action == "exec":
481
+ result = shell.exec(args.cmd, args.cwd, args.timeout)
482
+ elif action == "exec-script":
483
+ result = shell.exec_script(args.script, args.shell, args.cwd, args.timeout)
484
+ elif action == "info":
485
+ result = shell.info()
486
+ else:
487
+ print(f"Unknown shell action: {action}", file=sys.stderr)
488
+ sys.exit(1)
489
+ _print_json(_maybe_wait_for_result(
490
+ client, result, args.wait_result, args.wait_timeout, args.poll_interval
491
+ ))
492
+
493
+ elif args.command == "agent":
494
+ agent = AgentCommands(client)
495
+ action = args.agent_action
496
+ if action == "ps":
497
+ result = agent.ps(
498
+ args.script,
499
+ cwd=args.cwd,
500
+ timeout_sec=args.timeout,
501
+ wait_timeout=args.wait_timeout,
502
+ poll_interval=args.poll_interval,
503
+ )
504
+ elif action == "cmd":
505
+ result = agent.cmd(
506
+ args.cmd,
507
+ cwd=args.cwd,
508
+ timeout_sec=args.timeout,
509
+ wait_timeout=args.wait_timeout,
510
+ poll_interval=args.poll_interval,
511
+ )
512
+ elif action == "open":
513
+ result = agent.open(
514
+ args.app,
515
+ launch_path=args.launch_path,
516
+ app_args=args.app_args,
517
+ start_if_missing=not args.no_start,
518
+ foreground=not args.no_foreground,
519
+ launch_timeout=args.launch_timeout,
520
+ wait_timeout=args.wait_timeout,
521
+ poll_interval=args.poll_interval,
522
+ session_id=args.session,
523
+ use_window_cache=not args.no_window_cache,
524
+ )
525
+ elif action == "observe":
526
+ result = agent.observe(
527
+ args.app,
528
+ mode=args.mode,
529
+ output_format=args.observe_format,
530
+ element_limit=args.element_limit,
531
+ targets=_split_targets(args.targets),
532
+ wait_timeout=args.wait_timeout,
533
+ poll_interval=args.poll_interval,
534
+ session_id=args.session,
535
+ use_window_cache=not args.no_window_cache,
536
+ refresh_window=args.refresh_window,
537
+ )
538
+ elif action == "probe":
539
+ result = agent.probe(
540
+ args.app,
541
+ _split_targets(args.targets),
542
+ mode=args.mode,
543
+ wait_timeout=args.wait_timeout,
544
+ poll_interval=args.poll_interval,
545
+ session_id=args.session,
546
+ use_window_cache=not args.no_window_cache,
547
+ refresh_window=args.refresh_window,
548
+ )
549
+ elif action == "click":
550
+ result = agent.click(
551
+ args.app,
552
+ args.target,
553
+ locate_mode=args.locate_mode,
554
+ wait_timeout=args.wait_timeout,
555
+ poll_interval=args.poll_interval,
556
+ session_id=args.session,
557
+ use_window_cache=not args.no_window_cache,
558
+ refresh_window=args.refresh_window,
559
+ )
560
+ elif action == "type":
561
+ result = agent.type_text(
562
+ args.app,
563
+ args.target,
564
+ args.text,
565
+ locate_mode=args.locate_mode,
566
+ wait_timeout=args.wait_timeout,
567
+ poll_interval=args.poll_interval,
568
+ session_id=args.session,
569
+ use_window_cache=not args.no_window_cache,
570
+ refresh_window=args.refresh_window,
571
+ )
572
+ elif action == "key":
573
+ result = agent.key(
574
+ args.app,
575
+ args.key,
576
+ wait_timeout=args.wait_timeout,
577
+ poll_interval=args.poll_interval,
578
+ session_id=args.session,
579
+ use_window_cache=not args.no_window_cache,
580
+ refresh_window=args.refresh_window,
581
+ )
582
+ elif action == "hotkey":
583
+ result = agent.hotkey(
584
+ args.app,
585
+ args.keys,
586
+ wait_timeout=args.wait_timeout,
587
+ poll_interval=args.poll_interval,
588
+ session_id=args.session,
589
+ use_window_cache=not args.no_window_cache,
590
+ refresh_window=args.refresh_window,
591
+ )
592
+ elif action == "scroll":
593
+ result = agent.scroll(
594
+ args.app,
595
+ direction=args.direction,
596
+ amount=args.amount,
597
+ by=args.by,
598
+ dispatch=args.dispatch,
599
+ wait_timeout=args.wait_timeout,
600
+ poll_interval=args.poll_interval,
601
+ session_id=args.session,
602
+ use_window_cache=not args.no_window_cache,
603
+ refresh_window=args.refresh_window,
604
+ )
605
+ elif action == "session-clear":
606
+ result = agent.clear_session(args.session)
607
+ else:
608
+ print(f"Unknown agent action: {action}", file=sys.stderr)
609
+ sys.exit(1)
610
+ _print_json(result)
611
+
612
+ elif args.command == "result":
613
+ data = client.poll_result(
614
+ args.trace_id,
615
+ timeout_sec=args.wait_timeout,
616
+ interval=args.poll_interval,
617
+ )
618
+ _print_json(data)
619
+
620
+ else:
621
+ parser.print_help()
622
+
623
+
624
+ if __name__ == "__main__":
625
+ try:
626
+ main()
627
+ except Exception as e:
628
+ import traceback
629
+ _print_json({
630
+ "status": "rejected",
631
+ "reason": "cli_error",
632
+ "error": str(e),
633
+ "traceback": traceback.format_exc(),
634
+ })
635
+ sys.exit(1)