@frontendxlab/opencode-computer-use 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,733 @@
1
+ <!doctype html>
2
+ <html lang="en" data-theme="dark">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <meta name="description" content="Install OpenCode Computer Use, a local OpenCode plugin that registers cua_repl for persistent JavaScript desktop control, with native runtimes for Linux, macOS, and Windows.">
7
+ <meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large">
8
+ <meta name="author" content="rashidtvmr">
9
+ <meta name="publisher" content="FrontendX Labs">
10
+ <meta name="application-name" content="OpenCode Computer Use">
11
+ <meta name="theme-color" content="#0a0a0a" media="(prefers-color-scheme: dark)">
12
+ <meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
13
+ <link rel="canonical" href="https://opencode-computer-use.pages.dev/">
14
+ <link rel="manifest" href="/site.webmanifest">
15
+ <link rel="alternate" type="text/plain" href="/llms.txt" title="Machine-readable project summary">
16
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
17
+ <link rel="apple-touch-icon" href="/og-card.png">
18
+ <meta property="og:type" content="website">
19
+ <meta property="og:title" content="OpenCode Computer Use | cua_repl Plugin Guide">
20
+ <meta property="og:description" content="Install and use a Codex-style cua_repl desktop-control session in OpenCode and OpenCode2.">
21
+ <meta property="og:url" content="https://opencode-computer-use.pages.dev/">
22
+ <meta property="og:site_name" content="OpenCode Computer Use">
23
+ <meta property="og:locale" content="en_US">
24
+ <meta property="og:image" content="https://opencode-computer-use.pages.dev/og-card.png">
25
+ <meta property="og:image:width" content="1200">
26
+ <meta property="og:image:height" content="630">
27
+ <meta property="og:image:alt" content="OpenCode Computer Use documentation">
28
+ <meta name="twitter:card" content="summary_large_image">
29
+ <meta name="twitter:title" content="OpenCode Computer Use | cua_repl Plugin Guide">
30
+ <meta name="twitter:description" content="Install and use a Codex-style cua_repl desktop-control session in OpenCode and OpenCode2.">
31
+ <meta name="twitter:image" content="https://opencode-computer-use.pages.dev/og-card.png">
32
+ <title>OpenCode Computer Use | cua_repl Plugin Guide</title>
33
+ <script type="application/ld+json">
34
+ {
35
+ "@context": "https://schema.org",
36
+ "@graph": [
37
+ {
38
+ "@type": "Organization",
39
+ "@id": "https://opencode-computer-use.pages.dev/#organization",
40
+ "name": "FrontendX Labs",
41
+ "alternateName": "rashidtvmr",
42
+ "url": "https://github.com/rashidtvmr",
43
+ "sameAs": [
44
+ "https://github.com/rashidtvmr/opencode-computer-use"
45
+ ]
46
+ },
47
+ {
48
+ "@type": "WebSite",
49
+ "@id": "https://opencode-computer-use.pages.dev/#website",
50
+ "url": "https://opencode-computer-use.pages.dev/",
51
+ "name": "OpenCode Computer Use",
52
+ "description": "Documentation for a local OpenCode plugin with a persistent cua_repl JavaScript desktop-control session.",
53
+ "inLanguage": "en",
54
+ "publisher": {
55
+ "@id": "https://opencode-computer-use.pages.dev/#organization"
56
+ }
57
+ },
58
+ {
59
+ "@type": "SoftwareApplication",
60
+ "@id": "https://opencode-computer-use.pages.dev/#software",
61
+ "name": "OpenCode Computer Use",
62
+ "description": "A dual-generation OpenCode plugin for local desktop control through a persistent cua_repl JavaScript session.",
63
+ "url": "https://opencode-computer-use.pages.dev/",
64
+ "softwareVersion": "0.1.0",
65
+ "datePublished": "2026-09-25",
66
+ "dateModified": "2026-09-25",
67
+ "isAccessibleForFree": true,
68
+ "downloadUrl": "https://www.npmjs.com/package/@frontendxlab/opencode-computer-use",
69
+ "applicationCategory": "DeveloperApplication",
70
+ "operatingSystem": "Linux, macOS, Windows",
71
+ "softwareRequirements": "Node.js 18 or newer; OpenCode 1.18.29 or newer or OpenCode2 V2; a signed-in graphical desktop session.",
72
+ "codeRepository": "https://github.com/rashidtvmr/opencode-computer-use",
73
+ "license": "https://github.com/rashidtvmr/opencode-computer-use/blob/main/LICENSE",
74
+ "featureList": [
75
+ "Local stdio MCP server named cua_repl",
76
+ "Persistent JavaScript session with app bindings",
77
+ "Accessibility-state and screenshot inspection",
78
+ "Element-first desktop actions"
79
+ ],
80
+ "author": {
81
+ "@id": "https://opencode-computer-use.pages.dev/#organization"
82
+ },
83
+ "publisher": {
84
+ "@id": "https://opencode-computer-use.pages.dev/#organization"
85
+ },
86
+ "isPartOf": {
87
+ "@id": "https://opencode-computer-use.pages.dev/#website"
88
+ }
89
+ },
90
+ {
91
+ "@type": "TechArticle",
92
+ "@id": "https://opencode-computer-use.pages.dev/#article",
93
+ "headline": "OpenCode Computer Use: Codex-style desktop control for OpenCode",
94
+ "description": "Setup, API, security, operating-system, and troubleshooting guidance for the OpenCode Computer Use plugin.",
95
+ "datePublished": "2026-09-25",
96
+ "dateModified": "2026-09-25",
97
+ "url": "https://opencode-computer-use.pages.dev/",
98
+ "inLanguage": "en",
99
+ "articleSection": "Developer tools",
100
+ "keywords": [
101
+ "OpenCode plugin",
102
+ "cua_repl",
103
+ "Model Context Protocol",
104
+ "JavaScript desktop control",
105
+ "computer use"
106
+ ],
107
+ "mainEntityOfPage": {
108
+ "@id": "https://opencode-computer-use.pages.dev/#website"
109
+ },
110
+ "about": {
111
+ "@id": "https://opencode-computer-use.pages.dev/#software"
112
+ },
113
+ "author": {
114
+ "@id": "https://opencode-computer-use.pages.dev/#organization"
115
+ },
116
+ "publisher": {
117
+ "@id": "https://opencode-computer-use.pages.dev/#organization"
118
+ },
119
+ "isPartOf": {
120
+ "@id": "https://opencode-computer-use.pages.dev/#website"
121
+ }
122
+ },
123
+ {
124
+ "@type": "FAQPage",
125
+ "@id": "https://opencode-computer-use.pages.dev/#faq",
126
+ "url": "https://opencode-computer-use.pages.dev/#faq",
127
+ "mainEntity": [
128
+ {
129
+ "@type": "Question",
130
+ "name": "Is OpenCode Computer Use a plugin, an MCP server, or both?",
131
+ "acceptedAnswer": {
132
+ "@type": "Answer",
133
+ "text": "Both layers are present. The OpenCode plugin loads the local package and registers an MCP server named cua_repl. That server exposes only js and js_reset to the model."
134
+ }
135
+ },
136
+ {
137
+ "@type": "Question",
138
+ "name": "What do js and js_reset do?",
139
+ "acceptedAnswer": {
140
+ "@type": "Answer",
141
+ "text": "js evaluates JavaScript in the persistent session, so variables and app bindings survive between calls. js_reset discards those bindings but does not reset native application state."
142
+ }
143
+ },
144
+ {
145
+ "@type": "Question",
146
+ "name": "Which operating systems are supported?",
147
+ "acceptedAnswer": {
148
+ "@type": "Answer",
149
+ "text": "Optional native runtime packages target Linux, macOS, and Windows on x64 and arm64. Live capabilities depend on OS permissions, the desktop session, and the target app; macOS and Windows still require live validation."
150
+ }
151
+ },
152
+ {
153
+ "@type": "Question",
154
+ "name": "Does the plugin bypass operating-system permissions?",
155
+ "acceptedAnswer": {
156
+ "@type": "Answer",
157
+ "text": "No. It cannot control a lock screen, login screen, or UAC secure desktop. OpenCode approvals and operating-system permissions remain the enforcement layer."
158
+ }
159
+ },
160
+ {
161
+ "@type": "Question",
162
+ "name": "Does the model get raw desktop tools or an arbitrary shell?",
163
+ "acceptedAnswer": {
164
+ "@type": "Answer",
165
+ "text": "No. The model sees only js and js_reset, not raw desktop tools or run_shell. The restricted JavaScript session also blocks filesystem, process, fetch, dynamic import, and code-generation access."
166
+ }
167
+ },
168
+ {
169
+ "@type": "Question",
170
+ "name": "How do I verify the installation?",
171
+ "acceptedAnswer": {
172
+ "@type": "Answer",
173
+ "text": "Restart OpenCode or reopen the TUI, run opencode mcp list, and confirm that cua_repl is connected before starting a desktop task."
174
+ }
175
+ }
176
+ ]
177
+ }
178
+ ]
179
+ }
180
+ </script>
181
+ <script>
182
+ document.documentElement.dataset.theme = localStorage.getItem("computer-use-theme") || "dark";
183
+ </script>
184
+ <style>
185
+ :root,
186
+ [data-theme="dark"] {
187
+ --bg: #0a0a0a;
188
+ --panel: #171717;
189
+ --panel-2: #222222;
190
+ --text: #ededed;
191
+ --muted: #a1a1a1;
192
+ --faint: #8a8a8a;
193
+ --line: #333333;
194
+ --line-strong: #555555;
195
+ --signal: #3b82f6;
196
+ --signal-2: #60a5fa;
197
+ --mint: #34d399;
198
+ --blue: #60a5fa;
199
+ --danger: #ff9188;
200
+ --code: #93c5fd;
201
+ --gradient: linear-gradient(90deg, #8657a7 0%, #cd3678 24%, #ff1e56 44%, #3f79d5 74%, #0196ff 100%);
202
+ --shadow: rgba(0, 0, 0, .4);
203
+ --shadow-sm: 0 2px 10px rgba(0, 0, 0, .3);
204
+ --shadow-md: 0 16px 44px rgba(0, 0, 0, .4);
205
+ --sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
206
+ --nav: "Montserrat Alternates", var(--sans);
207
+ --mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
208
+ color-scheme: dark;
209
+ font: 16px/1.65 var(--sans);
210
+ }
211
+
212
+ :root[data-theme="light"] {
213
+ --bg: #ffffff;
214
+ --panel: #fafafa;
215
+ --panel-2: #f5f5f5;
216
+ --text: #171717;
217
+ --muted: #737373;
218
+ --faint: #a3a3a3;
219
+ --line: #e5e5e5;
220
+ --line-strong: #a3a3a3;
221
+ --signal: #2563eb;
222
+ --signal-2: #1d4ed8;
223
+ --mint: #047857;
224
+ --blue: #2563eb;
225
+ --danger: #b91c1c;
226
+ --code: #1d4ed8;
227
+ --gradient: linear-gradient(90deg, #8657a7 0%, #cd3678 24%, #ff1e56 44%, #3f79d5 74%, #0196ff 100%);
228
+ --shadow: rgba(0, 0, 0, .12);
229
+ --shadow-sm: 0 2px 10px rgba(0, 0, 0, .08);
230
+ --shadow-md: 0 16px 44px rgba(0, 0, 0, .12);
231
+ color-scheme: light;
232
+ }
233
+
234
+ * { box-sizing: border-box; }
235
+ html { scroll-behavior: smooth; background: var(--bg); }
236
+ body { margin: 0; overflow-x: hidden; color: var(--text); background: var(--bg); }
237
+ body::before {
238
+ position: fixed;
239
+ inset: 0;
240
+ z-index: -1;
241
+ pointer-events: none;
242
+ content: "";
243
+ background: radial-gradient(ellipse 80% 50% at 50% -20%, rgba(134, 87, 167, .18), transparent 72%);
244
+ }
245
+ a { color: inherit; }
246
+ button, input { font: inherit; }
247
+ button, .button { min-height: 42px; padding: 0 14px; border: 1px solid var(--line); border-radius: 12px; color: var(--text); background: var(--panel); cursor: pointer; }
248
+ button:hover, .button:hover { border-color: var(--signal); }
249
+ button:active, .button:active { transform: translateY(1px); }
250
+ :focus-visible { outline: 3px solid color-mix(in srgb, var(--signal) 50%, transparent); outline-offset: 3px; }
251
+ .button { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
252
+ .button.primary { border-color: var(--text); color: #171717; background: var(--text); font-weight: 750; }
253
+ [data-theme="light"] .button.primary { border-color: #171717; color: #ffffff; background: #171717; }
254
+ .skip { position: fixed; top: 8px; left: 8px; z-index: 20; padding: 10px 14px; color: var(--bg); background: var(--text); transform: translateY(-150%); }
255
+ .skip:focus { transform: none; }
256
+
257
+ .top { position: fixed; top: 8px; left: 16px; right: 16px; z-index: 10; width: min(1100px, calc(100% - 32px)); margin: auto; border: 1px solid color-mix(in srgb, var(--line) 80%, transparent); border-radius: 24px; background: color-mix(in srgb, var(--panel-2) 88%, transparent); box-shadow: 0 6px 28px rgba(0, 0, 0, .22); backdrop-filter: blur(20px); }
258
+ .top-inner { display: flex; align-items: center; gap: 24px; width: 100%; height: 52px; margin: auto; padding: 0 16px; }
259
+ .mark { min-width: 0; font: 750 14.5px/1 var(--nav); letter-spacing: -.02em; text-decoration: none; white-space: nowrap; }
260
+ .mark span { color: var(--signal-2); }
261
+ .top nav { display: flex; gap: 20px; margin-left: auto; }
262
+ .top nav a { color: color-mix(in srgb, var(--text) 58%, transparent); font: 500 14.5px/1 var(--nav); text-decoration: none; }
263
+ .top nav a:hover { color: var(--text); }
264
+ #theme { min-height: 36px; padding-inline: 11px; font-size: 13px; white-space: nowrap; }
265
+
266
+ .hero { display: grid; grid-template-columns: minmax(0, 1.02fr) minmax(360px, .98fr); gap: clamp(42px, 7vw, 110px); align-items: center; width: min(1240px, calc(100% - 44px)); min-height: calc(100dvh - 66px); margin: auto; padding: 108px 0 92px; }
267
+ .hero > *, .layout > *, .hero-copy, .console { min-width: 0; }
268
+ .hero-copy { position: relative; padding-top: 20px; }
269
+ .hero-copy::before, .hero-copy::after { display: block; height: 1px; margin: 0 -22px 20px; content: ""; background: var(--gradient); }
270
+ .hero-copy::after { margin: 22px -22px 0; }
271
+ .eyebrow { margin: 0; color: var(--signal); font: 750 12px/1.2 var(--mono); letter-spacing: .16em; text-transform: uppercase; }
272
+ h1 { max-width: 760px; margin: 17px 0 23px; font-size: clamp(3.4rem, 7.4vw, 7rem); line-height: .92; letter-spacing: -.025em; }
273
+ h1 .gradient-word { background: var(--gradient); -webkit-background-clip: text; background-clip: text; color: transparent; }
274
+ .lede { max-width: 610px; margin: 0; color: var(--muted); font-size: clamp(1.05rem, 1.6vw, 1.28rem); }
275
+ .hero-actions { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 30px; }
276
+ .hero-note { display: flex; gap: 9px; align-items: center; margin-top: 20px; color: var(--faint); font-size: 12px; }
277
+ .pulse { width: 8px; height: 8px; border-radius: 50%; background: var(--mint); box-shadow: 0 0 0 5px color-mix(in srgb, var(--mint) 15%, transparent); }
278
+
279
+ .console { position: relative; padding: 18px; border: 1px solid var(--line); border-radius: 20px; background: color-mix(in srgb, var(--panel) 96%, transparent); box-shadow: var(--shadow-md); }
280
+ .console::before { position: absolute; top: -11px; right: 24px; width: 74px; height: 22px; border: 1px solid var(--line); border-bottom: 0; border-radius: 12px 12px 0 0; content: ""; background: var(--panel-2); }
281
+ .console-head { display: flex; align-items: center; justify-content: space-between; gap: 16px; margin-bottom: 16px; }
282
+ .console-head strong { font-size: 14px; }
283
+ .status { display: inline-flex; align-items: center; gap: 7px; color: var(--mint); font: 12px var(--mono); }
284
+ .status i { width: 7px; height: 7px; border-radius: 50%; background: currentColor; }
285
+ .console pre { max-width: 100%; margin: 0; padding: 17px; border: 1px solid var(--line); border-radius: 12px; color: var(--text); background: var(--panel-2); font: 13px/1.7 var(--mono); }
286
+ [data-theme="light"] .console pre { background: var(--panel-2); color: var(--text); }
287
+ .console pre .key { color: var(--blue); }
288
+ .console pre .fn { color: var(--signal-2); }
289
+ .console pre .str { color: var(--mint); }
290
+ .console-foot { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 13px; }
291
+ .tag { padding: 5px 9px; border: 1px solid var(--line); border-radius: 999px; color: var(--muted); font: 12px ui-monospace, SFMono-Regular, Menlo, monospace; }
292
+ .tag strong { color: var(--signal); }
293
+
294
+ .layout { display: grid; grid-template-columns: 180px minmax(0, 760px) minmax(170px, 1fr); gap: 48px; width: min(1240px, calc(100% - 44px)); margin: auto; padding-bottom: 90px; }
295
+ .toc, .rail { position: sticky; top: 92px; align-self: start; }
296
+ .toc strong, .rail strong { display: block; margin-bottom: 10px; font-size: 12px; letter-spacing: .08em; text-transform: uppercase; }
297
+ .toc a { display: block; padding: 6px 0; color: var(--muted); font-size: 13px; text-decoration: none; }
298
+ .toc a:hover, .toc a[aria-current="true"] { color: var(--signal); }
299
+ .rail { padding: 15px 0 15px 16px; border-left: 1px solid var(--line); }
300
+ .rail p { margin: 0 0 17px; color: var(--muted); font-size: 12px; }
301
+ .rail a { color: var(--signal); }
302
+ .mobile-toc { display: none; }
303
+
304
+ section { scroll-margin-top: 88px; padding: 17px 0 57px; border-top: 1px solid var(--line); }
305
+ section h2 { margin: 10px 0 14px; font-size: clamp(1.8rem, 4vw, 2.75rem); line-height: 1.02; letter-spacing: -.025em; }
306
+ section h3 { margin: 27px 0 9px; font-size: 1.08rem; }
307
+ p, li { color: var(--muted); }
308
+ p { margin: 10px 0; }
309
+ ul, ol { padding-left: 21px; }
310
+ code, pre { font: 13px/1.65 var(--mono); }
311
+ code { color: var(--code); }
312
+ pre { overflow: auto; margin: 0; padding: 15px; border: 1px solid var(--line); border-radius: 12px; color: var(--text); background: var(--panel-2); }
313
+ .code { position: relative; margin: 14px 0; }
314
+ .code pre { padding-right: 78px; }
315
+ .copy { position: absolute; top: 8px; right: 8px; min-height: 32px; padding-inline: 10px; font-size: 12px; }
316
+ .note { margin: 18px 0; padding: 14px 16px; border-left: 3px solid var(--signal); color: var(--muted); background: color-mix(in srgb, var(--signal) 8%, var(--panel)); }
317
+ .note strong { color: var(--text); }
318
+ .tabs { display: flex; gap: 7px; margin: 19px 0 13px; }
319
+ .tabs button[aria-selected="true"] { border-color: var(--signal-2); color: var(--signal-2); background: color-mix(in srgb, var(--signal) 8%, var(--panel)); }
320
+ [role="tabpanel"][hidden] { display: none; }
321
+ .steps { display: grid; gap: 11px; padding: 0; list-style: none; counter-reset: step; }
322
+ .steps li { position: relative; padding: 15px 16px 15px 57px; border: 1px solid var(--line); border-radius: 9px; background: var(--panel); }
323
+ .steps li::before { position: absolute; top: 15px; left: 16px; display: grid; width: 26px; height: 26px; place-items: center; border: 1px solid var(--signal); border-radius: 50%; color: var(--signal); content: counter(step); font: 750 12px ui-monospace, monospace; counter-increment: step; }
324
+ .cards { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 0; list-style: none; }
325
+ .cards li { padding: 16px; border: 1px solid var(--line); border-radius: 9px; background: var(--panel); }
326
+ .cards strong { color: var(--text); }
327
+ .cards span { display: block; margin-top: 5px; color: var(--muted); font-size: 13px; }
328
+ .definition-list { display: grid; gap: 9px; margin: 18px 0 0; }
329
+ .definition-list > div { display: grid; grid-template-columns: 155px minmax(0, 1fr); gap: 15px; padding: 14px 15px; border: 1px solid var(--line); border-radius: 9px; background: var(--panel); }
330
+ .definition-list dt { color: var(--text); font-weight: 750; }
331
+ .definition-list dd { margin: 0; color: var(--muted); }
332
+ .matrix { width: 100%; border-collapse: collapse; font-size: 14px; }
333
+ .matrix caption { margin-bottom: 8px; color: var(--muted); font-size: 12px; text-align: left; }
334
+ .matrix th, .matrix td { padding: 11px 12px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; }
335
+ .matrix th { color: var(--text); font-size: 12px; letter-spacing: .06em; text-transform: uppercase; }
336
+ .matrix td { color: var(--muted); }
337
+ .yes { color: var(--mint); font-weight: 700; }
338
+ .pending { color: var(--signal); font-weight: 700; }
339
+ .faq-list { margin-top: 18px; border-top: 1px solid var(--line); }
340
+ .faq-list > div { padding: 14px 0; border-bottom: 1px solid var(--line); }
341
+ .faq-list h3 { margin: 0 0 5px; }
342
+ .faq-list p { margin: 0; }
343
+ details { padding: 12px 0; border-top: 1px solid var(--line); }
344
+ summary { cursor: pointer; color: var(--text); font-weight: 700; }
345
+ details p { margin-bottom: 0; }
346
+ .builder { display: grid; gap: 12px; margin-top: 15px; padding: 16px; border: 1px solid var(--line); border-radius: 10px; background: var(--panel); }
347
+ .builder label, .builder legend { display: grid; gap: 6px; color: var(--muted); font-size: 12px; font-weight: 700; }
348
+ .builder input[type="text"] { width: 100%; height: 42px; padding: 0 11px; border: 1px solid var(--line); border-radius: 7px; color: var(--text); background: var(--bg); }
349
+ .builder fieldset { min-width: 0; margin: 0; padding: 0; border: 0; }
350
+ .segments { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; }
351
+ .segments label { display: grid; min-height: 42px; place-items: center; padding: 0 7px; border: 1px solid var(--line); border-radius: 7px; color: var(--muted); cursor: pointer; text-align: center; }
352
+ .segments label:has(input:checked) { border-color: var(--signal-2); color: var(--signal-2); background: color-mix(in srgb, var(--signal) 8%, var(--panel)); }
353
+ .segments input { position: absolute; opacity: 0; }
354
+ .builder .row { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
355
+ .builder output { display: block; min-height: 80px; margin: 0; padding: 12px; border: 1px solid var(--line); border-radius: 12px; color: var(--mint); background: var(--panel-2); font: 12px/1.6 var(--mono); white-space: pre-wrap; }
356
+ footer { display: flex; justify-content: space-between; gap: 20px; width: min(1240px, calc(100% - 44px)); margin: auto; padding: 27px 0 40px; border-top: 1px solid var(--line); color: var(--muted); font-size: 13px; }
357
+ footer a { color: var(--signal); }
358
+ .sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; }
359
+
360
+ @media (max-width: 1000px) {
361
+ .hero { grid-template-columns: 1fr; min-height: auto; padding-top: 70px; }
362
+ .layout { grid-template-columns: 1fr; max-width: 820px; }
363
+ .toc, .rail { display: none; }
364
+ .mobile-toc { display: block; margin-bottom: 24px; }
365
+ .top nav { display: none; }
366
+ }
367
+ @media (max-width: 600px) {
368
+ .top { left: 8px; right: 8px; width: calc(100% - 16px); }
369
+ .top-inner, .hero, .layout, footer { width: min(100% - 28px, 1240px); }
370
+ h1 { font-size: 3.45rem; }
371
+ .cards, .builder .row { grid-template-columns: 1fr; }
372
+ .definition-list > div { grid-template-columns: 1fr; gap: 5px; }
373
+ .console { box-shadow: var(--shadow-md); }
374
+ footer { display: block; }
375
+ footer span { display: block; margin-top: 8px; }
376
+ }
377
+ @media (prefers-reduced-motion: reduce) {
378
+ html { scroll-behavior: auto; }
379
+ *, *::before, *::after { transition: none !important; }
380
+ }
381
+ </style>
382
+ </head>
383
+ <body>
384
+ <a class="skip" href="#content">Skip to documentation</a>
385
+ <header class="top">
386
+ <div class="top-inner">
387
+ <a class="mark" href="#top">OpenCode <span>/ Computer Use</span></a>
388
+ <nav aria-label="Primary navigation">
389
+ <a href="#install">Install</a>
390
+ <a href="#first-run">First run</a>
391
+ <a href="#api">API</a>
392
+ <a href="#security">Security</a>
393
+ <a href="https://github.com/rashidtvmr/opencode-computer-use">GitHub</a>
394
+ </nav>
395
+ <button id="theme" type="button" aria-pressed="false">Light theme</button>
396
+ </div>
397
+ </header>
398
+
399
+ <main id="content">
400
+ <section class="hero" id="top">
401
+ <div class="hero-copy">
402
+ <p class="eyebrow">Codex-style desktop control for OpenCode</p>
403
+ <h1>Desktop control with a <span class="gradient-word">memory.</span></h1>
404
+ <p class="lede">A small, persistent <code>cua_repl</code> session for OpenCode and OpenCode2. Bind an app, inspect its accessibility state, act, and verify the result without exposing a pile of raw desktop tools.</p>
405
+ <div class="hero-actions">
406
+ <a class="button primary" href="#install">Install the plugin</a>
407
+ <a class="button" href="#first-run">Run the first call</a>
408
+ </div>
409
+ <p class="hero-note"><span class="pulse" aria-hidden="true"></span> Local stdio MCP session, with native OS permissions still enforced.</p>
410
+ </div>
411
+ <div class="console" role="region" aria-label="cua_repl session example">
412
+ <div class="console-head"><strong>Session / Chrome</strong><span class="status"><i aria-hidden="true"></i> cua_repl example</span></div>
413
+ <pre><code><span class="key">var</span> chrome = <span class="fn">await</span> cua.<span class="fn">getApp</span>(<span class="str">"Google Chrome"</span>);
414
+ <span class="key">await</span> chrome.<span class="fn">getAXState</span>();
415
+ <span class="key">await</span> chrome.<span class="fn">click</span>([<span class="str">530</span>, <span class="str">20</span>]);</code></pre>
416
+ <div class="console-foot"><span class="tag"><strong>js</strong> persistent</span><span class="tag"><strong>js_reset</strong> clean slate</span><span class="tag">no shell tool</span></div>
417
+ </div>
418
+ </section>
419
+
420
+ <div class="layout">
421
+ <nav class="toc" aria-label="On this page">
422
+ <strong>On this page</strong>
423
+ <a href="#quick-answer">Quick answer</a>
424
+ <a href="#install">Install</a>
425
+ <a href="#first-run">First run</a>
426
+ <a href="#architecture">Architecture</a>
427
+ <a href="#api">API surface</a>
428
+ <a href="#webmcp">WebMCP</a>
429
+ <a href="#options">Options</a>
430
+ <a href="#workflow">Workflow</a>
431
+ <a href="#os">OS support</a>
432
+ <a href="#security">Security</a>
433
+ <a href="#troubleshooting">Troubleshooting</a>
434
+ <a href="#faq">FAQ</a>
435
+ </nav>
436
+
437
+ <article>
438
+ <details class="mobile-toc"><summary>On this page</summary><p><a href="#quick-answer">Quick answer</a> · <a href="#install">Install</a> · <a href="#first-run">First run</a> · <a href="#api">API</a> · <a href="#webmcp">WebMCP</a> · <a href="#security">Security</a> · <a href="#faq">FAQ</a></p></details>
439
+
440
+ <section id="quick-answer" data-section>
441
+ <h2>Short answer.</h2>
442
+ <p>OpenCode Computer Use is an OpenCode plugin that registers a local MCP server named <code>cua_repl</code>. The model receives two tools: <code>js</code> for persistent JavaScript and <code>js_reset</code> for a clean JavaScript session. Native operating-system permissions still control desktop access.</p>
443
+ <dl class="definition-list">
444
+ <div><dt>Plugin</dt><dd>The package loaded by OpenCode. It manages registration and lifecycle.</dd></div>
445
+ <div><dt>MCP server</dt><dd>The local stdio server named <code>cua_repl</code>.</dd></div>
446
+ <div><dt>Model tools</dt><dd><code>js</code> and <code>js_reset</code>, not raw desktop tools.</dd></div>
447
+ <div><dt>Native runtime</dt><dd><code>open-computer-use</code>, running separately with OS accessibility and capture permissions.</dd></div>
448
+ <div><dt>Supported hosts</dt><dd>OpenCode 1 and OpenCode2 on Linux, macOS, and Windows where the native runtime and permissions are available.</dd></div>
449
+ </dl>
450
+ </section>
451
+
452
+ <section id="install" data-section>
453
+ <h2>Install once. Keep the session.</h2>
454
+ <p>This repository is a standalone OpenCode plugin. It contains the plugin entrypoint, the local MCP server, and the native runtime integration. The plugin registers <code>cua_repl</code>; the model sees only <code>js</code> and <code>js_reset</code>.</p>
455
+ <div class="code"><pre><code id="clone">git clone git@github.com:rashidtvmr/opencode-computer-use.git
456
+ cd opencode-computer-use
457
+ npm install</code></pre><button class="copy" type="button" data-copy="#clone">Copy</button></div>
458
+ <p>For a published-package install, use:</p>
459
+ <div class="code"><pre><code id="npm-install">npm install @frontendxlab/opencode-computer-use</code></pre><button class="copy" type="button" data-copy="#npm-install">Copy</button></div>
460
+ <div class="tabs" role="tablist" aria-label="OpenCode generation">
461
+ <button role="tab" id="tab-v1" aria-controls="panel-v1" aria-selected="true">OpenCode 1</button>
462
+ <button role="tab" id="tab-v2" aria-controls="panel-v2" aria-selected="false" tabindex="-1">OpenCode2</button>
463
+ </div>
464
+ <div id="panel-v1" role="tabpanel" aria-labelledby="tab-v1">
465
+ <p>Add the checkout to the V1 <code>plugin</code> list. An absolute path avoids ambiguity when OpenCode starts from another directory.</p>
466
+ <div class="code"><pre><code id="config-v1">{
467
+ "$schema": "https://opencode.ai/config.json",
468
+ "plugin": [
469
+ ["@frontendxlab/opencode-computer-use", { "backend": "auto" }]
470
+ ]
471
+ }</code></pre><button class="copy" type="button" data-copy="#config-v1">Copy</button></div>
472
+ <p>Restart OpenCode or reopen the TUI, then verify the dynamically registered server:</p>
473
+ <div class="code"><pre><code id="verify">opencode mcp list</code></pre><button class="copy" type="button" data-copy="#verify">Copy</button></div>
474
+ </div>
475
+ <div id="panel-v2" role="tabpanel" aria-labelledby="tab-v2" hidden>
476
+ <p>Use the V2 <code>plugins</code> shape when installing the checkout with OpenCode2.</p>
477
+ <div class="code"><pre><code id="config-v2">{
478
+ "$schema": "https://opencode.ai/config.json",
479
+ "plugins": [
480
+ {
481
+ "package": "@frontendxlab/opencode-computer-use",
482
+ "options": { "backend": "auto" }
483
+ }
484
+ ]
485
+ }</code></pre><button class="copy" type="button" data-copy="#config-v2">Copy</button></div>
486
+ <p>Confirm that <code>cua_repl</code> is connected before starting a desktop task.</p>
487
+ </div>
488
+ <div class="note"><strong>Why it is not listed as a command?</strong> The package is a plugin. During loading, its entrypoint registers a local MCP server named <code>cua_repl</code>. That server then exposes the callable tools <code>cua_repl.js</code> and <code>cua_repl.js_reset</code>.</div>
489
+ </section>
490
+
491
+ <section id="first-run" data-section>
492
+ <h2>Start with discovery.</h2>
493
+ <p>Use the first call to see what the native runtime can actually see. Do not guess an app name or assume that a screenshot is available.</p>
494
+ <div class="code"><pre><code id="discovery">var apps = await cua.listApps({ emit: false });
495
+ var chrome = await cua.getApp("Google Chrome");
496
+ await chrome.getAXState({ textLimit: 12000 });</code></pre><button class="copy" type="button" data-copy="#discovery">Copy</button></div>
497
+ <div class="builder" aria-label="Build a first cua_repl call">
498
+ <div class="row">
499
+ <label for="app-name">App name or PID<input id="app-name" type="text" value="Google Chrome" spellcheck="false"></label>
500
+ <fieldset><legend>Action</legend><div class="segments" role="radiogroup" aria-label="Action">
501
+ <label><input type="radio" name="action-kind" value="state" checked>Read state</label>
502
+ <label><input type="radio" name="action-kind" value="screenshot">Screenshot</label>
503
+ <label><input type="radio" name="action-kind" value="click">Click</label>
504
+ </div></fieldset>
505
+ </div>
506
+ <output id="generated-code" aria-live="polite"></output>
507
+ </div>
508
+ <p>The generated snippet is intentionally small. Bindings persist between calls, so use <code>var</code> when a later call may need to reassign the same name.</p>
509
+ </section>
510
+
511
+ <section id="architecture" data-section>
512
+ <h2>One surface. Three layers.</h2>
513
+ <ol class="steps">
514
+ <li><strong>OpenCode plugin</strong><br>Loads the local package and registers the MCP server without exposing raw desktop tools.</li>
515
+ <li><strong>cua_repl MCP server</strong><br>Provides a persistent JavaScript evaluation surface, output bounds, request validation, and worker isolation.</li>
516
+ <li><strong>Native runtime</strong><br>Runs <code>open-computer-use</code> as a separate child process. OS accessibility, screen capture, and input permissions remain there.</li>
517
+ </ol>
518
+ <div class="note">The JavaScript VM is a capability guard, not a complete security boundary. OpenCode approvals and operating-system permissions remain the enforcement layer.</div>
519
+ </section>
520
+
521
+ <section id="api" data-section>
522
+ <h2>The Codex-shaped API.</h2>
523
+ <p>The model receives a small async API inside persistent JavaScript:</p>
524
+ <ul class="cards">
525
+ <li><strong>cua.getState(options?)</strong><span>List the current desktop apps and emit bounded output.</span></li>
526
+ <li><strong>cua.listApps(options?)</strong><span>Return structured app records without emitting by default.</span></li>
527
+ <li><strong>cua.getApp(nameOrPid)</strong><span>Bind an app and emit its initial accessibility state.</span></li>
528
+ <li><strong>app.getAXState(options?)</strong><span>Read a fresh semantic tree before choosing an element index.</span></li>
529
+ <li><strong>app.getScreenshot(options?)</strong><span>Return local image bytes and optionally emit them with nodeRepl.</span></li>
530
+ <li><strong>app.click(target, options?)</strong><span>Use an element index first, or a bounded coordinate when necessary.</span></li>
531
+ <li><strong>app.typeText(text)</strong><span>Send literal text through the native runtime.</span></li>
532
+ <li><strong>app.pressKey(key)</strong><span>Send a named key or supported modifier chord.</span></li>
533
+ <li><strong>app.performSecondaryAction(index, action)</strong><span>Invoke an advertised accessibility action such as a context action.</span></li>
534
+ </ul>
535
+ <p>Use <code>nodeRepl.write(value)</code> for extra text and <code>await nodeRepl.emitImage(bytesOrDataUrl)</code> for local images. Both are bounded before crossing the worker boundary.</p>
536
+ </section>
537
+
538
+ <section id="webmcp" data-section>
539
+ <h2>WebMCP, when available.</h2>
540
+ <p>The documentation page includes a small, read-only WebMCP enhancement based on the current <code>document.modelContext.registerTool()</code> draft. Browsers without the preview API continue normally; no desktop-control or mutating tool is exposed through the page.</p>
541
+ <div class="code"><pre><code id="webmcp-code">const tools = await document.modelContext.getTools();
542
+ // Preview-only discovery: search_docs, get_install_config, get_api_reference</code></pre><button class="copy" type="button" data-copy="#webmcp-code">Copy</button></div>
543
+ <p class="note"><strong>Preview boundary:</strong> WebMCP support is experimental and requires a supporting browser, a secure context, and the <code>tools</code> Permissions Policy. Cloudflare Browser Run lab sessions can be used to test discovery. This page does not claim stable or universal browser support.</p>
544
+ </section>
545
+
546
+ <section id="options" data-section>
547
+ <h2>Make risky capabilities explicit.</h2>
548
+ <div class="code"><pre><code id="options-json">{
549
+ "backend": "auto",
550
+ "serverName": "cua_repl",
551
+ "timeoutMs": 30000,
552
+ "maxTextChars": 200000,
553
+ "maxImageBytes": 2097152,
554
+ "allowGlobalPointerFallbacks": false
555
+ }</code></pre><button class="copy" type="button" data-copy="#options-json">Copy</button></div>
556
+ <ul class="cards">
557
+ <li><strong>backend</strong><span><code>auto</code>, <code>native</code>, or a fixed <code>custom</code> command.</span></li>
558
+ <li><strong>serverName</strong><span>Rename the generated MCP server when a project needs isolation.</span></li>
559
+ <li><strong>timeoutMs</strong><span>Bounds JavaScript evaluation. A timeout resets the worker session.</span></li>
560
+ <li><strong>maxTextChars</strong><span>Aggregate text budget for one tool result.</span></li>
561
+ <li><strong>maxImageBytes</strong><span>Aggregate image budget for one tool result.</span></li>
562
+ <li><strong>allowGlobalPointerFallbacks</strong><span>Off by default. Enable only when system-level pointer movement is explicitly intended.</span></li>
563
+ </ul>
564
+ </section>
565
+
566
+ <section id="workflow" data-section>
567
+ <h2>Observe, act, verify.</h2>
568
+ <ol class="steps">
569
+ <li>Read <code>getAXState()</code> before selecting an element.</li>
570
+ <li>Prefer a fresh element index over a remembered coordinate.</li>
571
+ <li>Batch deterministic actions and the next state read in one <code>js</code> call.</li>
572
+ <li>After navigation, dialogs, resizing, or rerendering, read state again.</li>
573
+ <li>Use <code>js_reset</code> when bindings need to be discarded, not when an app needs restarting.</li>
574
+ </ol>
575
+ <div class="code"><pre><code id="workflow-code">var app = await cua.getApp("Example App");
576
+ await app.getAXState();
577
+ var state = await app.click(12);
578
+ await app.getAXState();
579
+ nodeRepl.write(state);</code></pre><button class="copy" type="button" data-copy="#workflow-code">Copy</button></div>
580
+ </section>
581
+
582
+ <section id="os" data-section>
583
+ <h2>Know what the host can do.</h2>
584
+ <table class="matrix">
585
+ <caption>Operating-system runtime targets and current validation status</caption>
586
+ <thead><tr><th scope="col">Target</th><th scope="col">Packaged runtime</th><th scope="col">Live status here</th></tr></thead>
587
+ <tbody>
588
+ <tr><td>Linux x64</td><td class="yes">Included</td><td class="yes">Read-only smoke passed</td></tr>
589
+ <tr><td>Linux arm64</td><td class="yes">Included</td><td class="pending">Needs arm64 host</td></tr>
590
+ <tr><td>macOS arm64 and x64</td><td class="yes">Included</td><td class="pending">Needs live permissions</td></tr>
591
+ <tr><td>Windows arm64 and x64</td><td class="yes">Included</td><td class="pending">Needs UIA validation</td></tr>
592
+ </tbody>
593
+ </table>
594
+ <p>macOS generally needs Accessibility and Screen Recording. Windows needs UI Automation in the interactive desktop session. Linux needs AT-SPI plus the native runtime's input and capture services. A secure desktop, lock screen, UAC surface, or missing permission is not bypassed.</p>
595
+ <div class="note"><strong>Current Linux finding:</strong> on a GNOME Wayland session, Chrome exposed a pseudo-window with no actionable AT-SPI children. The native runtime accepted some synthetic events without changing the window. The adapter reports that result honestly; it does not turn a no-op into a success claim.</div>
596
+ </section>
597
+
598
+ <section id="security" data-section>
599
+ <h2>Small surface. Strong boundaries.</h2>
600
+ <ul class="cards">
601
+ <li><strong>No arbitrary shell</strong><span>The model-facing API does not expose <code>run_shell</code> or a host command escape.</span></li>
602
+ <li><strong>Restricted VM</strong><span>Filesystem, process, fetch, dynamic import, code generation, and host built-ins are blocked.</span></li>
603
+ <li><strong>Worker isolation</strong><span>CPU-bound JavaScript can be terminated without wedging the MCP server.</span></li>
604
+ <li><strong>Bounded transport</strong><span>Input frames, output text, images, content items, and native responses are bounded.</span></li>
605
+ <li><strong>Approval preserving</strong><span>Consequential actions still pass through OpenCode and OS permission boundaries.</span></li>
606
+ <li><strong>Safe defaults</strong><span>Global pointer fallback is disabled until explicitly enabled.</span></li>
607
+ </ul>
608
+ <p>Read <code>SECURITY.md</code> before using the session against banking, password managers, administrator tools, or unattended workloads.</p>
609
+ </section>
610
+
611
+ <section id="troubleshooting" data-section>
612
+ <h2>When the session is quiet.</h2>
613
+ <details><summary><code>cua_repl</code> is not listed</summary><p>Check that the plugin path is in the active OpenCode generation's configuration, restart the TUI, and run <code>opencode mcp list</code>. Do not register the same plugin and MCP server twice.</p></details>
614
+ <details><summary>Node cannot be found</summary><p>Set <code>OPENCODE_NODE</code> to an absolute Node executable before starting OpenCode, or make <code>node</code> available on the server PATH.</p></details>
615
+ <details><summary>The app list is empty</summary><p>Run <code>open-computer-use list-apps</code> in the same graphical session. Confirm the app is already running and that the desktop accessibility session is available.</p></details>
616
+ <details><summary>Screenshots are unavailable</summary><p>Screen capture depends on the OS compositor and permissions. The plugin can still provide bounded accessibility text when capture is unavailable.</p></details>
617
+ <details><summary>Linux input returns success but nothing changes</summary><p>Check the desktop session type and native runtime prerequisites. A pseudo-window or Wayland compositor can expose state without accepting synthetic input. Observe again before retrying a mutating action.</p></details>
618
+ <details><summary>Global pointer fallback is rejected</summary><p>This is the expected safety gate. Enable <code>allowGlobalPointerFallbacks</code> only for an explicit diagnostic session where moving the real pointer and changing focus are acceptable.</p></details>
619
+ </section>
620
+
621
+ <section id="faq" data-section>
622
+ <h2>Frequently asked questions.</h2>
623
+ <div class="faq-list">
624
+ <div><h3>Is OpenCode Computer Use a plugin, an MCP server, or both?</h3><p>Both layers are present. The OpenCode plugin loads the local package and registers an MCP server named cua_repl. That server exposes only js and js_reset to the model.</p></div>
625
+ <div><h3>What do js and js_reset do?</h3><p>js evaluates JavaScript in the persistent session, so variables and app bindings survive between calls. js_reset discards those bindings but does not reset native application state.</p></div>
626
+ <div><h3>Which operating systems are supported?</h3><p>Optional native runtime packages target Linux, macOS, and Windows on x64 and arm64. Live capabilities depend on OS permissions, the desktop session, and the target app; macOS and Windows still require live validation.</p></div>
627
+ <div><h3>Does the plugin bypass operating-system permissions?</h3><p>No. It cannot control a lock screen, login screen, or UAC secure desktop. OpenCode approvals and operating-system permissions remain the enforcement layer.</p></div>
628
+ <div><h3>Does the model get raw desktop tools or an arbitrary shell?</h3><p>No. The model sees only js and js_reset, not raw desktop tools or run_shell. The restricted JavaScript session also blocks filesystem, process, fetch, dynamic import, and code-generation access.</p></div>
629
+ <div><h3>How do I verify the installation?</h3><p>Restart OpenCode or reopen the TUI, run opencode mcp list, and confirm that cua_repl is connected before starting a desktop task.</p></div>
630
+ </div>
631
+ </section>
632
+ </article>
633
+
634
+ <aside class="rail">
635
+ <strong>Surface map</strong>
636
+ <p>Plugin: <code>opencode-computer-use</code><br>MCP: <code>cua_repl</code><br>Tools: <code>js</code>, <code>js_reset</code></p>
637
+ <strong>Default policy</strong>
638
+ <p>Local stdio only. Global pointer fallback off. Native OS permissions enforced.</p>
639
+ <strong>Updated</strong>
640
+ <p>Published September 25, 2026. Updated September 25, 2026.</p>
641
+ <strong>WebMCP</strong>
642
+ <p>Preview support registers read-only documentation tools when the browser exposes the WebMCP API. Normal browsers remain fully supported.</p>
643
+ <strong>Repository</strong>
644
+ <p><a href="https://github.com/rashidtvmr/opencode-computer-use">Source and issues</a></p>
645
+ </aside>
646
+ </div>
647
+ </main>
648
+
649
+ <footer>
650
+ <span>OpenCode Computer Use</span>
651
+ <span><a href="https://github.com/rashidtvmr/opencode-computer-use">GitHub</a> · <a href="https://github.com/rashidtvmr/opencode-computer-use/blob/main/README.md">README</a></span>
652
+ </footer>
653
+ <div class="sr" id="live-region" aria-live="polite"></div>
654
+
655
+ <script src="/webmcp.js" defer></script>
656
+ <script>
657
+ const root = document.documentElement;
658
+ const themeButton = document.querySelector("#theme");
659
+ const live = document.querySelector("#live-region");
660
+ const paintTheme = () => {
661
+ const light = root.dataset.theme === "light";
662
+ themeButton.textContent = light ? "Dark theme" : "Light theme";
663
+ themeButton.setAttribute("aria-pressed", String(light));
664
+ };
665
+ paintTheme();
666
+ themeButton.addEventListener("click", () => {
667
+ root.dataset.theme = root.dataset.theme === "light" ? "dark" : "light";
668
+ localStorage.setItem("computer-use-theme", root.dataset.theme);
669
+ paintTheme();
670
+ });
671
+
672
+ document.querySelectorAll("[data-copy]").forEach((button) => {
673
+ button.addEventListener("click", async () => {
674
+ const text = document.querySelector(button.dataset.copy)?.textContent || "";
675
+ try {
676
+ await navigator.clipboard.writeText(text);
677
+ button.textContent = "Copied";
678
+ live.textContent = "Copied to clipboard";
679
+ } catch {
680
+ button.textContent = "Copy failed";
681
+ live.textContent = "Copy failed";
682
+ }
683
+ window.setTimeout(() => { button.textContent = "Copy"; }, 1500);
684
+ });
685
+ });
686
+
687
+ const tabs = [...document.querySelectorAll('[role="tab"]')];
688
+ const selectTab = (tab) => {
689
+ tabs.forEach((item) => {
690
+ const active = item === tab;
691
+ item.setAttribute("aria-selected", String(active));
692
+ item.tabIndex = active ? 0 : -1;
693
+ document.querySelector(`#${item.getAttribute("aria-controls")}`).hidden = !active;
694
+ });
695
+ tab.focus();
696
+ };
697
+ tabs.forEach((tab, index) => {
698
+ tab.addEventListener("click", () => selectTab(tab));
699
+ tab.addEventListener("keydown", (event) => {
700
+ if (event.key === "ArrowRight") selectTab(tabs[(index + 1) % tabs.length]);
701
+ if (event.key === "ArrowLeft") selectTab(tabs[(index - 1 + tabs.length) % tabs.length]);
702
+ if (event.key === "Home") selectTab(tabs[0]);
703
+ if (event.key === "End") selectTab(tabs.at(-1));
704
+ });
705
+ });
706
+
707
+ const appName = document.querySelector("#app-name");
708
+ const actionKind = () => document.querySelector('input[name="action-kind"]:checked')?.value || "state";
709
+ const generated = document.querySelector("#generated-code");
710
+ const updateGenerated = () => {
711
+ const app = appName.value.trim() || "Google Chrome";
712
+ const action = actionKind();
713
+ const lines = [`var app = await cua.getApp(${JSON.stringify(app)});`];
714
+ if (action === "state") lines.push("await app.getAXState({ textLimit: 12000 });");
715
+ if (action === "screenshot") lines.push("await app.getScreenshot({ emit: true });");
716
+ if (action === "click") lines.push("await app.click([530, 20], { clickMethod: \"auto\" });");
717
+ generated.textContent = lines.join("\n");
718
+ };
719
+ appName.addEventListener("input", updateGenerated);
720
+ document.querySelectorAll('input[name="action-kind"]').forEach((input) => input.addEventListener("change", updateGenerated));
721
+ updateGenerated();
722
+
723
+ const links = [...document.querySelectorAll(".toc a")];
724
+ const sections = [...document.querySelectorAll("[data-section]")];
725
+ const observer = new IntersectionObserver((entries) => {
726
+ entries.forEach((entry) => {
727
+ if (entry.isIntersecting) links.forEach((link) => link.setAttribute("aria-current", String(link.hash === `#${entry.target.id}`)));
728
+ });
729
+ }, { rootMargin: "-20% 0px -70%" });
730
+ sections.forEach((section) => observer.observe(section));
731
+ </script>
732
+ </body>
733
+ </html>