@zenalexa/unicli 0.221.1 → 0.222.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.
Files changed (177) hide show
  1. package/AGENTS.md +2 -2
  2. package/README.md +128 -23
  3. package/README.zh-CN.md +115 -35
  4. package/crates/unicli-atspi/src/errors.rs +2 -2
  5. package/crates/unicli-uia/src/errors.rs +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +3 -0
  8. package/dist/cli.js.map +1 -1
  9. package/dist/commands/compute.d.ts.map +1 -1
  10. package/dist/commands/compute.js +1 -1
  11. package/dist/commands/compute.js.map +1 -1
  12. package/dist/commands/delivery.d.ts +17 -0
  13. package/dist/commands/delivery.d.ts.map +1 -0
  14. package/dist/commands/delivery.js +577 -0
  15. package/dist/commands/delivery.js.map +1 -0
  16. package/dist/commands/do.d.ts.map +1 -1
  17. package/dist/commands/do.js +3 -4
  18. package/dist/commands/do.js.map +1 -1
  19. package/dist/commands/doctor-compute.js +11 -7
  20. package/dist/commands/doctor-compute.js.map +1 -1
  21. package/dist/commands/extract.d.ts.map +1 -1
  22. package/dist/commands/extract.js +6 -2
  23. package/dist/commands/extract.js.map +1 -1
  24. package/dist/commands/lint.js +3 -3
  25. package/dist/commands/lint.js.map +1 -1
  26. package/dist/commands/migrate-schema.d.ts.map +1 -1
  27. package/dist/commands/migrate-schema.js +26 -22
  28. package/dist/commands/migrate-schema.js.map +1 -1
  29. package/dist/commands/runs.d.ts.map +1 -1
  30. package/dist/commands/runs.js +9 -5
  31. package/dist/commands/runs.js.map +1 -1
  32. package/dist/discovery/aliases.d.ts.map +1 -1
  33. package/dist/discovery/aliases.js +1 -0
  34. package/dist/discovery/aliases.js.map +1 -1
  35. package/dist/discovery/loader.d.ts.map +1 -1
  36. package/dist/discovery/loader.js +47 -2
  37. package/dist/discovery/loader.js.map +1 -1
  38. package/dist/discovery/search.d.ts.map +1 -1
  39. package/dist/discovery/search.js +20 -0
  40. package/dist/discovery/search.js.map +1 -1
  41. package/dist/electron-apps.d.ts +1 -1
  42. package/dist/electron-apps.d.ts.map +1 -1
  43. package/dist/electron-apps.js +2 -2
  44. package/dist/electron-apps.js.map +1 -1
  45. package/dist/engine/delivery/index.d.ts +20 -0
  46. package/dist/engine/delivery/index.d.ts.map +1 -0
  47. package/dist/engine/delivery/index.js +20 -0
  48. package/dist/engine/delivery/index.js.map +1 -0
  49. package/dist/engine/delivery/planner.d.ts +19 -0
  50. package/dist/engine/delivery/planner.d.ts.map +1 -0
  51. package/dist/engine/delivery/planner.js +322 -0
  52. package/dist/engine/delivery/planner.js.map +1 -0
  53. package/dist/engine/delivery/repair.d.ts +17 -0
  54. package/dist/engine/delivery/repair.d.ts.map +1 -0
  55. package/dist/engine/delivery/repair.js +86 -0
  56. package/dist/engine/delivery/repair.js.map +1 -0
  57. package/dist/engine/delivery/session.d.ts +17 -0
  58. package/dist/engine/delivery/session.d.ts.map +1 -0
  59. package/dist/engine/delivery/session.js +78 -0
  60. package/dist/engine/delivery/session.js.map +1 -0
  61. package/dist/engine/delivery/trajectory.d.ts +17 -0
  62. package/dist/engine/delivery/trajectory.d.ts.map +1 -0
  63. package/dist/engine/delivery/trajectory.js +166 -0
  64. package/dist/engine/delivery/trajectory.js.map +1 -0
  65. package/dist/engine/delivery/types.d.ts +157 -0
  66. package/dist/engine/delivery/types.d.ts.map +1 -0
  67. package/dist/engine/delivery/types.js +16 -0
  68. package/dist/engine/delivery/types.js.map +1 -0
  69. package/dist/engine/executor.d.ts +1 -1
  70. package/dist/engine/executor.js +7 -7
  71. package/dist/engine/executor.js.map +1 -1
  72. package/dist/engine/repair/remedies.js +3 -3
  73. package/dist/engine/repair/remedies.js.map +1 -1
  74. package/dist/engine/steps/desktop-ax.d.ts +4 -0
  75. package/dist/engine/steps/desktop-ax.d.ts.map +1 -1
  76. package/dist/engine/steps/desktop-ax.js +8 -0
  77. package/dist/engine/steps/desktop-ax.js.map +1 -1
  78. package/dist/engine/steps/desktop-sidecar.d.ts +1 -1
  79. package/dist/engine/steps/desktop-sidecar.js +1 -1
  80. package/dist/engine/steps/index.d.ts +1 -1
  81. package/dist/engine/steps/index.d.ts.map +1 -1
  82. package/dist/engine/steps/index.js +1 -1
  83. package/dist/engine/steps/index.js.map +1 -1
  84. package/dist/engine/steps/visual.d.ts +47 -0
  85. package/dist/engine/steps/visual.d.ts.map +1 -0
  86. package/dist/engine/steps/visual.js +66 -0
  87. package/dist/engine/steps/visual.js.map +1 -0
  88. package/dist/engine/transport/mcp-browser.d.ts +1 -1
  89. package/dist/engine/transport/mcp-browser.js +1 -1
  90. package/dist/manifest-compact.txt +2 -2
  91. package/dist/manifest-search.json +1 -1
  92. package/dist/manifest.json +69 -55
  93. package/dist/output/error-writer.d.ts +23 -0
  94. package/dist/output/error-writer.d.ts.map +1 -0
  95. package/dist/output/error-writer.js +20 -0
  96. package/dist/output/error-writer.js.map +1 -0
  97. package/dist/transport/adapters/desktop-atspi.js +1 -1
  98. package/dist/transport/adapters/desktop-atspi.js.map +1 -1
  99. package/dist/transport/adapters/desktop-ax-background-activation-swift.d.ts +9 -0
  100. package/dist/transport/adapters/desktop-ax-background-activation-swift.d.ts.map +1 -0
  101. package/dist/transport/adapters/desktop-ax-background-activation-swift.js +99 -0
  102. package/dist/transport/adapters/desktop-ax-background-activation-swift.js.map +1 -0
  103. package/dist/transport/adapters/desktop-ax-background-click-swift.d.ts.map +1 -1
  104. package/dist/transport/adapters/desktop-ax-background-click-swift.js +10 -133
  105. package/dist/transport/adapters/desktop-ax-background-click-swift.js.map +1 -1
  106. package/dist/transport/adapters/desktop-ax-background-click.d.ts +1 -3
  107. package/dist/transport/adapters/desktop-ax-background-click.d.ts.map +1 -1
  108. package/dist/transport/adapters/desktop-ax-background-click.js +1 -69
  109. package/dist/transport/adapters/desktop-ax-background-click.js.map +1 -1
  110. package/dist/transport/adapters/desktop-ax-background-dispatch-swift.d.ts +9 -0
  111. package/dist/transport/adapters/desktop-ax-background-dispatch-swift.d.ts.map +1 -0
  112. package/dist/transport/adapters/desktop-ax-background-dispatch-swift.js +169 -0
  113. package/dist/transport/adapters/desktop-ax-background-dispatch-swift.js.map +1 -0
  114. package/dist/transport/adapters/desktop-ax-background-input-swift.d.ts +23 -0
  115. package/dist/transport/adapters/desktop-ax-background-input-swift.d.ts.map +1 -0
  116. package/dist/transport/adapters/desktop-ax-background-input-swift.js +157 -0
  117. package/dist/transport/adapters/desktop-ax-background-input-swift.js.map +1 -0
  118. package/dist/transport/adapters/desktop-ax-background-input.d.ts +13 -0
  119. package/dist/transport/adapters/desktop-ax-background-input.d.ts.map +1 -0
  120. package/dist/transport/adapters/desktop-ax-background-input.js +124 -0
  121. package/dist/transport/adapters/desktop-ax-background-input.js.map +1 -0
  122. package/dist/transport/adapters/desktop-ax-background-window-swift.d.ts +9 -0
  123. package/dist/transport/adapters/desktop-ax-background-window-swift.d.ts.map +1 -0
  124. package/dist/transport/adapters/desktop-ax-background-window-swift.js +110 -0
  125. package/dist/transport/adapters/desktop-ax-background-window-swift.js.map +1 -0
  126. package/dist/transport/adapters/desktop-ax-swift.d.ts +1 -0
  127. package/dist/transport/adapters/desktop-ax-swift.d.ts.map +1 -1
  128. package/dist/transport/adapters/desktop-ax-swift.js +1 -0
  129. package/dist/transport/adapters/desktop-ax-swift.js.map +1 -1
  130. package/dist/transport/adapters/desktop-ax.d.ts +4 -1
  131. package/dist/transport/adapters/desktop-ax.d.ts.map +1 -1
  132. package/dist/transport/adapters/desktop-ax.js +57 -6
  133. package/dist/transport/adapters/desktop-ax.js.map +1 -1
  134. package/dist/transport/adapters/desktop-uia.js +1 -1
  135. package/dist/transport/adapters/desktop-uia.js.map +1 -1
  136. package/dist/transport/adapters/visual.d.ts +114 -0
  137. package/dist/transport/adapters/visual.d.ts.map +1 -0
  138. package/dist/transport/adapters/visual.js +473 -0
  139. package/dist/transport/adapters/visual.js.map +1 -0
  140. package/dist/transport/bus.d.ts +1 -1
  141. package/dist/transport/bus.js +3 -3
  142. package/dist/transport/bus.js.map +1 -1
  143. package/dist/transport/capability.d.ts.map +1 -1
  144. package/dist/transport/capability.js +32 -30
  145. package/dist/transport/capability.js.map +1 -1
  146. package/dist/transport/cascade.js +27 -27
  147. package/dist/transport/cascade.js.map +1 -1
  148. package/dist/transport/types.d.ts +5 -5
  149. package/dist/transport/types.d.ts.map +1 -1
  150. package/dist/transport/types.js +1 -1
  151. package/docs/operate/compute.md +20 -7
  152. package/docs/operate/focus-behavior.md +21 -12
  153. package/docs/operate/troubleshooting.md +17 -12
  154. package/package.json +6 -12
  155. package/server.json +2 -2
  156. package/skills/unicli/SKILL.md +1 -1
  157. package/skills/unicli-claude-code/SKILL.md +1 -1
  158. package/skills/unicli-hermes/SKILL.md +1 -1
  159. package/skills/unicli-repair/references/error-codes.md +7 -7
  160. package/src/adapters/_archived/README.md +6 -6
  161. package/src/adapters/_archived/apple-music/rate-album.yaml +15 -15
  162. package/src/adapters/_archived/archive.json +2 -2
  163. package/src/adapters/figma/export-selected.yaml +16 -16
  164. package/src/adapters/instagram/reels.yaml +5 -7
  165. package/src/adapters/openalex/works.test.ts +40 -32
  166. package/src/adapters/weread/shelf.yaml +1 -1
  167. package/src/adapters/zoom/toggle-mute.yaml +1 -1
  168. package/dist/engine/steps/cua.d.ts +0 -41
  169. package/dist/engine/steps/cua.d.ts.map +0 -1
  170. package/dist/engine/steps/cua.js +0 -59
  171. package/dist/engine/steps/cua.js.map +0 -1
  172. package/dist/transport/adapters/cua.d.ts +0 -239
  173. package/dist/transport/adapters/cua.d.ts.map +0 -1
  174. package/dist/transport/adapters/cua.js +0 -661
  175. package/dist/transport/adapters/cua.js.map +0 -1
  176. package/src/adapters/cua/bench-list.yaml +0 -28
  177. package/src/adapters/cua/bench-run.yaml +0 -40
@@ -1,661 +0,0 @@
1
- /**
2
- * CuaTransport — screenshot + VLM "Computer Use" transport.
3
- *
4
- * Unlike the other transports, CUA has no single protocol — it wraps a
5
- * pluggable "backend" that implements the primitive perception/action
6
- * verbs against a real computer-use model or emulator. v0.214 ships the
7
- * backend contract and provider selection behind `CUA_BACKEND`:
8
- *
9
- * - `anthropic` Anthropic `computer_20251124` beta tool, `ANTHROPIC_API_KEY`
10
- * - `trycua` trycua/cua compute-server v0.1.9+, `TRYCUA_API_KEY`
11
- * - `opencua` OpenCUA-72B via local vLLM, `OPENCUA_ENDPOINT`
12
- * - `scrapybara` Scrapybara Computer API, `SCRAPYBARA_API_KEY`
13
- * - `mock` in-memory stub used by tests and when no key is present
14
- *
15
- * Provider network paths are explicit stubs in v0.214. They are gated
16
- * behind the corresponding env keys and fail through the same structured
17
- * envelope path as every other transport. The MockBackend is always
18
- * available so the offline test suite runs without secrets.
19
- *
20
- * Design contract:
21
- * - `action()` NEVER throws
22
- * - all backends implement the same primitive verb surface (below)
23
- * - selection is deterministic: `selectCuaBackend()` returns the same
24
- * backend for the same env — no hidden state
25
- */
26
- import { err, exitCodeFor, ok } from "../../core/envelope.js";
27
- // ── Supported pipeline steps ────────────────────────────────────────────
28
- const CUA_STEPS = [
29
- "cua_snapshot",
30
- "cua_click",
31
- "cua_type",
32
- "cua_key",
33
- "cua_scroll",
34
- "cua_drag",
35
- "cua_wait",
36
- "cua_assert",
37
- "cua_ask",
38
- "cua_backend",
39
- "cua_launch",
40
- ];
41
- const CUA_CAPABILITY = {
42
- steps: CUA_STEPS,
43
- snapshotFormats: ["screenshot"],
44
- mutatesHost: true,
45
- };
46
- // ── Mock backend (offline, deterministic) ───────────────────────────────
47
- /** Fixed 1x1 transparent PNG used by {@link MockBackend.snapshot}. */
48
- const MOCK_PNG_BASE64 = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkAAIAAAoAAv/lxKUAAAAASUVORK5CYII=";
49
- /**
50
- * MockBackend — deterministic, offline CUA backend used when no provider
51
- * credentials are present and by every test in this file. Records the
52
- * sequence of calls on `history` so tests can assert against it without
53
- * having to stub `fetch`.
54
- */
55
- export class MockBackend {
56
- name = "mock";
57
- history = [];
58
- record(verb, ...args) {
59
- this.history.push({ verb, args });
60
- }
61
- async snapshot() {
62
- this.record("snapshot");
63
- return { base64: MOCK_PNG_BASE64, width: 1, height: 1, mime: "image/png" };
64
- }
65
- async click(x, y, button = "left") {
66
- this.record("click", x, y, button);
67
- }
68
- async type(text) {
69
- this.record("type", text);
70
- }
71
- async key(key) {
72
- this.record("key", key);
73
- }
74
- async scroll(dx, dy) {
75
- this.record("scroll", dx, dy);
76
- }
77
- async drag(fromX, fromY, toX, toY) {
78
- this.record("drag", fromX, fromY, toX, toY);
79
- }
80
- async wait(ms) {
81
- this.record("wait", ms);
82
- // Intentionally not calling setTimeout — tests want determinism.
83
- }
84
- async ask(question) {
85
- this.record("ask", question);
86
- return "yes";
87
- }
88
- async launch(app) {
89
- this.record("launch", app);
90
- }
91
- }
92
- // ── Real-backend stubs ──────────────────────────────────────────────────
93
- /**
94
- * Base class used by every real backend. All verbs throw `BackendNotReadyError`
95
- * by default; subclasses override what they support.
96
- *
97
- * Honesty note (2026-04-24): v0.214 ships `Anthropic`, `Trycua`, `OpenCua`
98
- * and `Scrapybara` as **provider stubs**. They declare the shape the real
99
- * backends will take, fail fast with a structured envelope when selected
100
- * without their credentials, and let downstream tests pin the Mock
101
- * backend for offline determinism. Full network implementations remain
102
- * out of scope for this file until a real provider backend lands with a
103
- * screen-source/planner composition.
104
- *
105
- * Users who want a real backend today can implement the `CuaBackend`
106
- * interface in their own code and inject it via
107
- * {@link CuaTransport.setBackend}, or provide a factory through the
108
- * `opts.backend` of the transport constructor. That surface is stable.
109
- */
110
- class BackendNotReadyError extends Error {
111
- backend;
112
- verb;
113
- hint;
114
- constructor(backend, verb, hint) {
115
- super(`cua backend "${backend}" is a provider stub — ${verb} not implemented (${hint})`);
116
- this.backend = backend;
117
- this.verb = verb;
118
- this.hint = hint;
119
- this.name = "BackendNotReadyError";
120
- }
121
- }
122
- /**
123
- * Anthropic `computer_use` tool stub. Selected when `CUA_BACKEND=anthropic`
124
- * and `ANTHROPIC_API_KEY` is set.
125
- *
126
- * IMPORTANT — two-layer architecture: the Anthropic Messages API is a
127
- * planner, not a screen capture service. A production Anthropic backend
128
- * MUST be composed with a screenshot source (macOS `screencapture`,
129
- * trycua sandbox, scrapybara VM) because the model receives screenshots
130
- * from the client and returns `tool_use` actions. The shipped backend is
131
- * intentionally a stub until a concrete screenshot-source/planner
132
- * composition is implemented.
133
- *
134
- * `tool_version` defaults to `"computer_20260301"` (the successor to
135
- * `computer_20251124` in the Sonnet 4.6 rollout); override via env
136
- * `ANTHROPIC_CUA_TOOL_VERSION` so operators can follow Anthropic's
137
- * release cadence without a code change.
138
- */
139
- export class AnthropicBackend {
140
- name = "anthropic";
141
- apiKey;
142
- model;
143
- toolVersion;
144
- constructor(apiKey, model = process.env.ANTHROPIC_CUA_MODEL ?? "claude-sonnet-4-6", toolVersion = process.env.ANTHROPIC_CUA_TOOL_VERSION ??
145
- "computer_20260301") {
146
- this.apiKey = apiKey;
147
- this.model = model;
148
- this.toolVersion = toolVersion;
149
- }
150
- async snapshot() {
151
- throw new BackendNotReadyError(this.name, "snapshot", `the Anthropic Messages API does not capture screens; pair ${this.name} with a screenshot source (screencapture/trycua/scrapybara) before enabling this backend`);
152
- }
153
- async click() {
154
- throw new BackendNotReadyError(this.name, "click", `stubbed until this backend owns a real /v1/messages action bridge for "${this.toolVersion}" left_click`);
155
- }
156
- async type() {
157
- throw new BackendNotReadyError(this.name, "type", `stubbed until this backend owns a real /v1/messages action bridge for "${this.toolVersion}" type`);
158
- }
159
- async key() {
160
- throw new BackendNotReadyError(this.name, "key", `stubbed until this backend owns a real /v1/messages action bridge for "${this.toolVersion}" key`);
161
- }
162
- async scroll() {
163
- throw new BackendNotReadyError(this.name, "scroll", `stubbed until this backend owns a real /v1/messages action bridge for "${this.toolVersion}" scroll`);
164
- }
165
- async drag() {
166
- throw new BackendNotReadyError(this.name, "drag", `stubbed until this backend owns a real /v1/messages action bridge for "${this.toolVersion}" left_click_drag`);
167
- }
168
- async wait(ms) {
169
- await new Promise((r) => setTimeout(r, Math.max(0, ms)));
170
- }
171
- async ask() {
172
- throw new BackendNotReadyError(this.name, "ask", "stubbed until this backend owns a real /v1/messages Q&A bridge; supply your own backend via CuaTransport.setBackend() today");
173
- }
174
- }
175
- /** trycua/cua compute-server v0.1.9+. Activated when `CUA_BACKEND=trycua`. */
176
- export class TrycuaBackend {
177
- name = "trycua";
178
- apiKey;
179
- endpoint;
180
- constructor(apiKey, endpoint = "https://api.cua.dev") {
181
- this.apiKey = apiKey;
182
- this.endpoint = endpoint;
183
- }
184
- async snapshot() {
185
- throw new BackendNotReadyError(this.name, "snapshot", "implement GET /v1/screen against the trycua compute-server");
186
- }
187
- async click() {
188
- throw new BackendNotReadyError(this.name, "click", "POST /v1/click");
189
- }
190
- async type() {
191
- throw new BackendNotReadyError(this.name, "type", "POST /v1/type");
192
- }
193
- async key() {
194
- throw new BackendNotReadyError(this.name, "key", "POST /v1/key");
195
- }
196
- async scroll() {
197
- throw new BackendNotReadyError(this.name, "scroll", "POST /v1/scroll");
198
- }
199
- async drag() {
200
- throw new BackendNotReadyError(this.name, "drag", "POST /v1/drag");
201
- }
202
- async wait(ms) {
203
- await new Promise((r) => setTimeout(r, Math.max(0, ms)));
204
- }
205
- }
206
- /** OpenCUA-72B via local vLLM. Activated when `CUA_BACKEND=opencua`. */
207
- export class OpenCuaBackend {
208
- endpoint;
209
- name = "opencua";
210
- constructor(endpoint) {
211
- this.endpoint = endpoint;
212
- }
213
- async snapshot() {
214
- throw new BackendNotReadyError(this.name, "snapshot", "implement vLLM OpenCUA-72B screenshot call at " + this.endpoint);
215
- }
216
- async click() {
217
- throw new BackendNotReadyError(this.name, "click", "implement vLLM action");
218
- }
219
- async type() {
220
- throw new BackendNotReadyError(this.name, "type", "implement vLLM action");
221
- }
222
- async key() {
223
- throw new BackendNotReadyError(this.name, "key", "implement vLLM action");
224
- }
225
- async scroll() {
226
- throw new BackendNotReadyError(this.name, "scroll", "implement vLLM action");
227
- }
228
- async drag() {
229
- throw new BackendNotReadyError(this.name, "drag", "implement vLLM action");
230
- }
231
- async wait(ms) {
232
- await new Promise((r) => setTimeout(r, Math.max(0, ms)));
233
- }
234
- }
235
- /** Scrapybara Computer API. Activated when `CUA_BACKEND=scrapybara`. */
236
- export class ScrapybaraBackend {
237
- name = "scrapybara";
238
- apiKey;
239
- endpoint;
240
- constructor(apiKey, endpoint = "https://api.scrapybara.com") {
241
- this.apiKey = apiKey;
242
- this.endpoint = endpoint;
243
- }
244
- async snapshot() {
245
- throw new BackendNotReadyError(this.name, "snapshot", "implement Scrapybara screenshot API");
246
- }
247
- async click() {
248
- throw new BackendNotReadyError(this.name, "click", "Scrapybara click");
249
- }
250
- async type() {
251
- throw new BackendNotReadyError(this.name, "type", "Scrapybara type");
252
- }
253
- async key() {
254
- throw new BackendNotReadyError(this.name, "key", "Scrapybara key");
255
- }
256
- async scroll() {
257
- throw new BackendNotReadyError(this.name, "scroll", "Scrapybara scroll");
258
- }
259
- async drag() {
260
- throw new BackendNotReadyError(this.name, "drag", "Scrapybara drag");
261
- }
262
- async wait(ms) {
263
- await new Promise((r) => setTimeout(r, Math.max(0, ms)));
264
- }
265
- }
266
- /**
267
- * Pure function — given an env snapshot, return the backend to use.
268
- *
269
- * Selection rules (first match wins):
270
- * 1. `CUA_BACKEND=anthropic` and ANTHROPIC_API_KEY → AnthropicBackend
271
- * 2. `CUA_BACKEND=trycua` and TRYCUA_API_KEY → TrycuaBackend
272
- * 3. `CUA_BACKEND=opencua` and OPENCUA_ENDPOINT → OpenCuaBackend
273
- * 4. `CUA_BACKEND=scrapybara` and SCRAPYBARA_API_KEY → ScrapybaraBackend
274
- * 5. otherwise → MockBackend
275
- *
276
- * If the user names a backend but the required env is missing, we return a
277
- * MockBackend tagged with a `missing` sentinel so the caller can emit a
278
- * structured `config_error` envelope on first use.
279
- */
280
- export function selectCuaBackend(env = process.env) {
281
- const requested = (env.CUA_BACKEND ?? "").toLowerCase();
282
- switch (requested) {
283
- case "anthropic":
284
- if (env.ANTHROPIC_API_KEY)
285
- return new AnthropicBackend(env.ANTHROPIC_API_KEY);
286
- return new MockBackend(); // ANTHROPIC_API_KEY missing → fallback
287
- case "trycua":
288
- if (env.TRYCUA_API_KEY)
289
- return new TrycuaBackend(env.TRYCUA_API_KEY);
290
- return new MockBackend();
291
- case "opencua":
292
- if (env.OPENCUA_ENDPOINT)
293
- return new OpenCuaBackend(env.OPENCUA_ENDPOINT);
294
- return new MockBackend();
295
- case "scrapybara":
296
- if (env.SCRAPYBARA_API_KEY)
297
- return new ScrapybaraBackend(env.SCRAPYBARA_API_KEY);
298
- return new MockBackend();
299
- case "mock":
300
- case "":
301
- return new MockBackend();
302
- default:
303
- // Unknown backend name — fall back to mock so action() can envelope.
304
- return new MockBackend();
305
- }
306
- }
307
- /**
308
- * CuaTransport routes `cua_*` pipeline steps to the active backend, wraps
309
- * every outcome in an envelope, and never throws from `action()`.
310
- */
311
- export class CuaTransport {
312
- kind = "cua";
313
- capability = CUA_CAPABILITY;
314
- backend;
315
- lastSnapshot = undefined;
316
- constructor(opts = {}) {
317
- this.backend = opts.backend ?? selectCuaBackend(opts.env);
318
- }
319
- /** Introspect the active backend (used by tests + `cua_backend` step). */
320
- get activeBackendName() {
321
- return this.backend.name;
322
- }
323
- /** Swap backends at runtime — the `cua_backend` step uses this. */
324
- setBackend(backend) {
325
- this.backend = backend;
326
- }
327
- async open(_ctx) {
328
- // No-op — CUA backends don't need pipeline vars today.
329
- }
330
- async snapshot(opts) {
331
- const format = opts?.format ?? "screenshot";
332
- if (this.lastSnapshot && format === this.lastSnapshot.format)
333
- return this.lastSnapshot;
334
- try {
335
- const raw = await this.backend.snapshot();
336
- const snap = {
337
- format: "screenshot",
338
- data: Buffer.from(raw.base64, "base64"),
339
- width: raw.width,
340
- height: raw.height,
341
- };
342
- this.lastSnapshot = snap;
343
- return snap;
344
- }
345
- catch {
346
- // Never throw from snapshot — return an empty JSON envelope.
347
- return { format: "json", data: JSON.stringify({ ok: false }) };
348
- }
349
- }
350
- async action(req) {
351
- const start = Date.now();
352
- try {
353
- const envelope = await this.dispatch(req);
354
- envelope.elapsedMs = Date.now() - start;
355
- return envelope;
356
- }
357
- catch (e) {
358
- const msg = e instanceof Error ? e.message : String(e);
359
- return err({
360
- transport: "cua",
361
- step: 0,
362
- action: req.kind,
363
- reason: `unexpected error in cua.${req.kind}: ${msg}`,
364
- suggestion: "check backend configuration and env vars",
365
- retryable: false,
366
- });
367
- }
368
- }
369
- async close() {
370
- this.lastSnapshot = undefined;
371
- }
372
- // ── internals ────────────────────────────────────────────────────────
373
- async dispatch(req) {
374
- switch (req.kind) {
375
- case "cua_snapshot":
376
- return this.doSnapshot();
377
- case "cua_click":
378
- return this.doClick(req.params);
379
- case "cua_type":
380
- return this.doType(req.params);
381
- case "cua_key":
382
- return this.doKey(req.params);
383
- case "cua_scroll":
384
- return this.doScroll(req.params);
385
- case "cua_drag":
386
- return this.doDrag(req.params);
387
- case "cua_wait":
388
- return this.doWait(req.params);
389
- case "cua_assert":
390
- return this.doAssert(req.params);
391
- case "cua_ask":
392
- return this.doAsk(req.params);
393
- case "cua_backend":
394
- return this.doBackendInfo();
395
- case "cua_launch":
396
- return this.doLaunch(req.params);
397
- default:
398
- return err({
399
- transport: "cua",
400
- step: 0,
401
- action: req.kind,
402
- reason: `unsupported action "${req.kind}" for cua transport`,
403
- suggestion: `cua transport supports: ${CUA_STEPS.join(", ")}`,
404
- minimum_capability: `cua.${req.kind}`,
405
- exit_code: exitCodeFor("usage_error"),
406
- });
407
- }
408
- }
409
- envelopeFromBackendError(verb, e) {
410
- const msg = e instanceof Error ? e.message : String(e);
411
- const notReady = e instanceof BackendNotReadyError;
412
- return err({
413
- transport: "cua",
414
- step: 0,
415
- action: verb,
416
- reason: msg,
417
- suggestion: notReady
418
- ? `install or configure the \`${this.backend.name}\` backend before calling cua.${verb}`
419
- : "inspect backend error and retry",
420
- minimum_capability: `cua.${verb}`,
421
- retryable: !notReady,
422
- exit_code: notReady
423
- ? exitCodeFor("config_error")
424
- : exitCodeFor("service_unavailable"),
425
- });
426
- }
427
- async doSnapshot() {
428
- try {
429
- const raw = await this.backend.snapshot();
430
- this.lastSnapshot = {
431
- format: "screenshot",
432
- data: Buffer.from(raw.base64, "base64"),
433
- width: raw.width,
434
- height: raw.height,
435
- };
436
- return ok({
437
- backend: this.backend.name,
438
- width: raw.width,
439
- height: raw.height,
440
- base64: raw.base64,
441
- });
442
- }
443
- catch (e) {
444
- return this.envelopeFromBackendError("snapshot", e);
445
- }
446
- }
447
- async doClick(params) {
448
- const x = typeof params.x === "number" ? params.x : undefined;
449
- const y = typeof params.y === "number" ? params.y : undefined;
450
- if (x === undefined || y === undefined) {
451
- return err({
452
- transport: "cua",
453
- step: 0,
454
- action: "cua_click",
455
- reason: "cua_click requires numeric params.x and params.y",
456
- suggestion: "pass { x, y } coordinates in pixel space",
457
- exit_code: exitCodeFor("usage_error"),
458
- });
459
- }
460
- const button = params.button === "right" ? "right" : "left";
461
- try {
462
- await this.backend.click(x, y, button);
463
- return ok({ x, y, button });
464
- }
465
- catch (e) {
466
- return this.envelopeFromBackendError("click", e);
467
- }
468
- }
469
- async doType(params) {
470
- const text = typeof params.text === "string" ? params.text : undefined;
471
- if (text === undefined) {
472
- return err({
473
- transport: "cua",
474
- step: 0,
475
- action: "cua_type",
476
- reason: "cua_type requires params.text (string)",
477
- suggestion: "pass the literal text to type",
478
- exit_code: exitCodeFor("usage_error"),
479
- });
480
- }
481
- try {
482
- await this.backend.type(text);
483
- return ok({ text });
484
- }
485
- catch (e) {
486
- return this.envelopeFromBackendError("type", e);
487
- }
488
- }
489
- async doKey(params) {
490
- const key = typeof params.key === "string" ? params.key : undefined;
491
- if (!key) {
492
- return err({
493
- transport: "cua",
494
- step: 0,
495
- action: "cua_key",
496
- reason: "cua_key requires params.key (string)",
497
- suggestion: 'pass a named key, e.g. "Return", "cmd+a"',
498
- exit_code: exitCodeFor("usage_error"),
499
- });
500
- }
501
- try {
502
- await this.backend.key(key);
503
- return ok({ key });
504
- }
505
- catch (e) {
506
- return this.envelopeFromBackendError("key", e);
507
- }
508
- }
509
- async doScroll(params) {
510
- const dx = typeof params.dx === "number" ? params.dx : 0;
511
- const dy = typeof params.dy === "number" ? params.dy : 0;
512
- try {
513
- await this.backend.scroll(dx, dy);
514
- return ok({ dx, dy });
515
- }
516
- catch (e) {
517
- return this.envelopeFromBackendError("scroll", e);
518
- }
519
- }
520
- async doDrag(params) {
521
- const fromX = typeof params.fromX === "number" ? params.fromX : undefined;
522
- const fromY = typeof params.fromY === "number" ? params.fromY : undefined;
523
- const toX = typeof params.toX === "number" ? params.toX : undefined;
524
- const toY = typeof params.toY === "number" ? params.toY : undefined;
525
- if (fromX === undefined ||
526
- fromY === undefined ||
527
- toX === undefined ||
528
- toY === undefined) {
529
- return err({
530
- transport: "cua",
531
- step: 0,
532
- action: "cua_drag",
533
- reason: "cua_drag requires params.fromX/fromY/toX/toY numbers",
534
- suggestion: "pass all four drag endpoints as numeric pixel coords",
535
- exit_code: exitCodeFor("usage_error"),
536
- });
537
- }
538
- try {
539
- await this.backend.drag(fromX, fromY, toX, toY);
540
- return ok({ fromX, fromY, toX, toY });
541
- }
542
- catch (e) {
543
- return this.envelopeFromBackendError("drag", e);
544
- }
545
- }
546
- async doWait(params) {
547
- const ms = typeof params.ms === "number"
548
- ? params.ms
549
- : typeof params.seconds === "number"
550
- ? params.seconds * 1000
551
- : 0;
552
- try {
553
- await this.backend.wait(Math.max(0, ms));
554
- return ok({ ms });
555
- }
556
- catch (e) {
557
- return this.envelopeFromBackendError("wait", e);
558
- }
559
- }
560
- async doAssert(params) {
561
- // The CUA assert is a ask→boolean — the VLM decides whether the
562
- // screen satisfies the predicate. With MockBackend it always passes.
563
- const predicate = typeof params.predicate === "string" ? params.predicate : undefined;
564
- if (!predicate) {
565
- return err({
566
- transport: "cua",
567
- step: 0,
568
- action: "cua_assert",
569
- reason: "cua_assert requires params.predicate (string)",
570
- suggestion: "pass a natural-language predicate for the VLM",
571
- exit_code: exitCodeFor("usage_error"),
572
- });
573
- }
574
- try {
575
- const answer = this.backend.ask
576
- ? await this.backend.ask(`Does the screen satisfy: ${predicate}?`)
577
- : "yes";
578
- const truthy = /^(y|yes|true|1)$/i.test(answer.trim());
579
- if (!truthy) {
580
- return err({
581
- transport: "cua",
582
- step: 0,
583
- action: "cua_assert",
584
- reason: `VLM did not confirm predicate "${predicate}" (got ${answer})`,
585
- suggestion: "inspect the latest snapshot or refine the predicate",
586
- retryable: true,
587
- });
588
- }
589
- return ok({ predicate, answer });
590
- }
591
- catch (e) {
592
- return this.envelopeFromBackendError("assert", e);
593
- }
594
- }
595
- async doAsk(params) {
596
- const question = typeof params.question === "string" ? params.question : undefined;
597
- if (!question) {
598
- return err({
599
- transport: "cua",
600
- step: 0,
601
- action: "cua_ask",
602
- reason: "cua_ask requires params.question (string)",
603
- suggestion: "pass a natural-language question for the VLM",
604
- exit_code: exitCodeFor("usage_error"),
605
- });
606
- }
607
- if (!this.backend.ask) {
608
- return err({
609
- transport: "cua",
610
- step: 0,
611
- action: "cua_ask",
612
- reason: `backend ${this.backend.name} does not implement ask()`,
613
- suggestion: "use the anthropic or mock backend, or add ask() to the backend",
614
- minimum_capability: "cua.ask",
615
- exit_code: exitCodeFor("service_unavailable"),
616
- });
617
- }
618
- try {
619
- const answer = await this.backend.ask(question);
620
- return ok({ question, answer });
621
- }
622
- catch (e) {
623
- return this.envelopeFromBackendError("ask", e);
624
- }
625
- }
626
- async doBackendInfo() {
627
- return ok({ backend: this.backend.name });
628
- }
629
- async doLaunch(params) {
630
- const app = typeof params.app === "string" ? params.app : undefined;
631
- if (!app) {
632
- return err({
633
- transport: "cua",
634
- step: 0,
635
- action: "cua_launch",
636
- reason: "cua_launch requires params.app (string)",
637
- suggestion: "pass the app name to launch",
638
- exit_code: exitCodeFor("usage_error"),
639
- });
640
- }
641
- if (!this.backend.launch) {
642
- return err({
643
- transport: "cua",
644
- step: 0,
645
- action: "cua_launch",
646
- reason: `backend ${this.backend.name} does not support launch()`,
647
- suggestion: "use desktop-ax.launch_app for native macOS launch, or Scrapybara sandbox",
648
- minimum_capability: "cua.launch",
649
- exit_code: exitCodeFor("service_unavailable"),
650
- });
651
- }
652
- try {
653
- await this.backend.launch(app);
654
- return ok({ app });
655
- }
656
- catch (e) {
657
- return this.envelopeFromBackendError("launch", e);
658
- }
659
- }
660
- }
661
- //# sourceMappingURL=cua.js.map