acstack 0.0.0-stage → 0.47.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.
Files changed (143) hide show
  1. package/.claude-plugin/plugin.json +19 -0
  2. package/LICENSE +21 -0
  3. package/README.md +389 -2
  4. package/THIRD_PARTY_NOTICES +14 -0
  5. package/TROUBLESHOOTING.md +225 -0
  6. package/agents/codex/frontend-verifier.toml +352 -0
  7. package/agents/codex/task-checker.toml +345 -0
  8. package/agents/codex/task-executor.toml +157 -0
  9. package/agents/codex/test-writer.toml +149 -0
  10. package/agents/frontend-verifier.md +351 -0
  11. package/agents/task-checker.md +344 -0
  12. package/agents/task-executor.md +156 -0
  13. package/agents/test-writer.md +148 -0
  14. package/assets/playwriter-bridge/build-launcher.ps1 +32 -0
  15. package/assets/playwriter-bridge/doctor.ps1 +450 -0
  16. package/assets/playwriter-bridge/launcher-src.cs +131 -0
  17. package/assets/playwriter-bridge/opener.js +330 -0
  18. package/assets/playwriter-bridge/opener.ps1 +63 -0
  19. package/assets/playwriter-bridge/register-tasks.ps1 +328 -0
  20. package/assets/playwriter-bridge/relay.ps1 +50 -0
  21. package/assets/playwriter-bridge/stop.ps1 +125 -0
  22. package/assets/playwriter-bridge/sync.ps1 +271 -0
  23. package/assets/playwriter-bridge/tunnel.ps1 +325 -0
  24. package/assets/playwriter-bridge/vm-free-port.sh +166 -0
  25. package/assets/terminal-workspace/client-keybindings.md +87 -0
  26. package/assets/terminal-workspace/config.example.yaml +329 -0
  27. package/assets/terminal-workspace/setup.sh +4317 -0
  28. package/cli/README.md +47 -0
  29. package/cli/bin/acstack.js +3 -0
  30. package/cli/dist/index.js +25984 -0
  31. package/cli/dist/index.js.map +7 -0
  32. package/cli/package.json +59 -0
  33. package/docs/adr/0001-ste-writing-standard.md +16 -0
  34. package/docs/adr/0002-version-gate-not-auto-bump.md +35 -0
  35. package/docs/adr/0003-handoff-ack-command.md +19 -0
  36. package/docs/adr/0004-dev-setup-per-host.md +13 -0
  37. package/docs/adr/0005-two-orchestrators-per-host.md +25 -0
  38. package/docs/adr/0006-document-opener-service.md +89 -0
  39. package/docs/adr/0007-lead-check-reads-the-transcript.md +87 -0
  40. package/docs/adr/0008-cli-audits-skill-interviews.md +37 -0
  41. package/docs/adr/0009-verify-the-write-not-the-writer.md +51 -0
  42. package/docs/adr/0010-promote-dev-to-main-by-fast-forward.md +115 -0
  43. package/docs/adr/0011-node-header-is-the-edge-encoding.md +20 -0
  44. package/docs/adr/0012-node-kind-is-a-closed-set-of-five.md +31 -0
  45. package/docs/adr/0013-the-diagram-renderer-is-pure.md +23 -0
  46. package/docs/adr/0014-reading-a-node-hides-its-generated-diagram.md +29 -0
  47. package/docs/adr/0015-a-node-id-links-to-its-issue-through-an-anchor.md +37 -0
  48. package/docs/adr/0016-the-map-root-carries-no-status-of-its-own.md +21 -0
  49. package/docs/adr/0017-call-impeccable-copy-only-its-questions.md +13 -0
  50. package/docs/adr/index.md +17 -0
  51. package/docs/config-reference.md +572 -0
  52. package/docs/dev-setups.md +164 -0
  53. package/docs/migrate-0.8.md +28 -0
  54. package/docs/playwriter-bridge-windows.md +15 -0
  55. package/docs/terminal-workspace-keys.md +137 -0
  56. package/docs/terminal-workspace.md +1774 -0
  57. package/docs/workflows.md +309 -0
  58. package/hooks/ac-writing-style-reminder.sh +86 -0
  59. package/hooks/context-pressure.sh +583 -0
  60. package/hooks/hooks.json +50 -0
  61. package/hooks/session-start-handoff.sh +165 -0
  62. package/hooks/session-start.sh +214 -0
  63. package/orchestrator/AGENTS.md +532 -0
  64. package/output-styles/ac-writing-style.md +286 -0
  65. package/package.json +50 -4
  66. package/pi/agents/frontend-verifier.md +357 -0
  67. package/pi/agents/task-checker.md +348 -0
  68. package/pi/agents/task-executor.md +160 -0
  69. package/pi/agents/test-writer.md +154 -0
  70. package/pi/extension.ts +723 -0
  71. package/profiles/fullstack-python.yaml +29 -0
  72. package/profiles/node-typescript.yaml +23 -0
  73. package/profiles/python.yaml +24 -0
  74. package/skills/archive/SKILL.md +160 -0
  75. package/skills/bootstrap/SKILL.md +109 -0
  76. package/skills/bridge/HANDOFF.md +93 -0
  77. package/skills/bridge/SKILL-VM.md +292 -0
  78. package/skills/bridge/SKILL-WINDOWS.md +526 -0
  79. package/skills/bridge/SKILL.md +121 -0
  80. package/skills/browser-verify/SKILL.md +229 -0
  81. package/skills/code-review/SKILL.md +44 -0
  82. package/skills/commit/SKILL.md +106 -0
  83. package/skills/compact-and-continue/SKILL.md +241 -0
  84. package/skills/explore/SKILL.md +149 -0
  85. package/skills/grilling/SKILL.md +134 -0
  86. package/skills/handoff/SKILL.md +250 -0
  87. package/skills/handoff/launch-herdr.md +197 -0
  88. package/skills/handoff/launch-without-herdr.md +30 -0
  89. package/skills/handoff/lead-check.md +59 -0
  90. package/skills/handoff/outcomes.md +162 -0
  91. package/skills/handoff/running-agent.md +50 -0
  92. package/skills/handoff/watch.md +115 -0
  93. package/skills/host/SKILL.md +247 -0
  94. package/skills/host/detect-host.sh +297 -0
  95. package/skills/host/herdr.md +79 -0
  96. package/skills/host/install-herdr-block.sh +467 -0
  97. package/skills/host/install-orca.sh +512 -0
  98. package/skills/host/notes.md +97 -0
  99. package/skills/host/orca.md +160 -0
  100. package/skills/host/questions.md +163 -0
  101. package/skills/host/report.md +42 -0
  102. package/skills/implement/SKILL.md +230 -0
  103. package/skills/implement/decision-log.md +62 -0
  104. package/skills/implement/design-pass.md +32 -0
  105. package/skills/implement/relaxed-tdd.md +28 -0
  106. package/skills/map/SKILL.md +250 -0
  107. package/skills/map/frontend.md +227 -0
  108. package/skills/map/trackers/files.md +74 -0
  109. package/skills/map/trackers/github.md +180 -0
  110. package/skills/map/visuals.md +387 -0
  111. package/skills/new-worktree/SKILL.md +155 -0
  112. package/skills/new-worktree/detect-orchestrator.sh +328 -0
  113. package/skills/new-worktree/detect-orchestrator.test.sh +894 -0
  114. package/skills/new-worktree/herdr.md +84 -0
  115. package/skills/new-worktree/orca.md +101 -0
  116. package/skills/pre-commit-review/SKILL.md +242 -0
  117. package/skills/pre-commit-review/auto-fix.md +19 -0
  118. package/skills/pre-commit-review/interactive.md +35 -0
  119. package/skills/quick-fix/SKILL.md +125 -0
  120. package/skills/record-context/SKILL.md +106 -0
  121. package/skills/setup/SKILL.md +250 -0
  122. package/skills/setup/browser.md +123 -0
  123. package/skills/setup/design-review.md +97 -0
  124. package/skills/setup/first-run.md +114 -0
  125. package/skills/setup/report.md +68 -0
  126. package/skills/setup/row-fixes.md +196 -0
  127. package/skills/setup/terminal-workspace.md +153 -0
  128. package/skills/sync/SKILL.md +171 -0
  129. package/skills/teardown-worktree/SKILL.md +245 -0
  130. package/skills/teardown-worktree/detector.md +39 -0
  131. package/skills/teardown-worktree/herdr.md +113 -0
  132. package/skills/teardown-worktree/orca.md +61 -0
  133. package/skills/terminal-workspace/SKILL.md +249 -0
  134. package/skills/terminal-workspace/components.md +36 -0
  135. package/skills/terminal-workspace/config-choices.md +69 -0
  136. package/skills/terminal-workspace/emulator-keys.md +48 -0
  137. package/skills/terminal-workspace/headless-clipboard.md +175 -0
  138. package/skills/terminal-workspace/turn-off-and-update.md +104 -0
  139. package/skills/test-conventions/SKILL.md +59 -0
  140. package/skills/visual-docs/SKILL.md +246 -0
  141. package/skills/writing/SKILL.md +231 -0
  142. package/skills/writing/examples.md +153 -0
  143. package/skills/writing/vocabulary.md +96 -0
@@ -0,0 +1,526 @@
1
+ # Playwriter bridge – Windows runbook
2
+
3
+ > Stop reading if you are not on Windows. This file is PowerShell-only.
4
+ > On Linux, macOS or a VM, read [`SKILL-VM.md`](SKILL-VM.md) instead.
5
+
6
+ ## What the Windows side owns
7
+
8
+ - The Node relay (`relay.ps1`) that speaks the Chrome DevTools Protocol (CDP)
9
+ on `127.0.0.1:19988`.
10
+
11
+ - The Playwriter Chrome extension that attaches to individual tabs.
12
+ - One reverse SSH tunnel per VM (`tunnel.ps1 -TargetHost <host>`).
13
+ - The document opener (`opener.ps1`) on `127.0.0.1:19989`, and its own
14
+ tunnel per VM. See [The document opener](#the-document-opener).
15
+
16
+ - The scheduled tasks that keep those running across logons.
17
+ - `ssh-agent` holding the key that authenticates the tunnels.
18
+
19
+ ## Architecture
20
+
21
+ ```
22
+ WINDOWS LAPTOP LINUX VM
23
+ ────────────── ────────
24
+ Chrome ("Playwriter Dev" profile)
25
+ + Playwriter extension (drives via chrome.debugger API)
26
+ │ localhost WebSocket
27
+ ▼
28
+ playwriter serve --host 127.0.0.1 ◄── listens on 127.0.0.1:19988 (Node relay)
29
+ ▲
30
+ │ outbound reverse SSH from laptop
31
+ │ ssh -N -R 19988:127.0.0.1:19988 user@vm
32
+ │
33
+ VM sshd binds 127.0.0.1:19988
34
+ │
35
+ ▼
36
+ agent-browser --cdp http://127.0.0.1:19988
37
+ ```
38
+
39
+ The bridge opens no debug port on Chrome. The laptop runs no inbound listener.
40
+ The only laptop-to-VM connection is outbound SSH.
41
+
42
+ ## Prerequisites
43
+
44
+ - Windows 10 / 11 laptop
45
+ - A Linux (or similar) VM reachable by SSH from the laptop
46
+ - Chrome installed on the laptop
47
+ - An SSH keypair you use to reach the VM
48
+
49
+ ## Initial setup
50
+
51
+ Run these from a normal (non-elevated) PowerShell unless a step says
52
+ otherwise.
53
+
54
+ ### 1. Install Node.js LTS
55
+
56
+ ```powershell
57
+ winget install OpenJS.NodeJS.LTS
58
+ ```
59
+
60
+ Open a new PowerShell so `node` and `npm` are on `PATH`.
61
+
62
+ ### 2. Install the Playwriter CLI
63
+
64
+ ```powershell
65
+ npm install -g playwriter
66
+ playwriter --version
67
+ ```
68
+
69
+ ### 3. Install the Playwriter Chrome extension (dedicated profile)
70
+
71
+ Create a new Chrome profile. Name it something like "Playwriter Dev".
72
+
73
+ Do **not** sign in to sensitive accounts on this profile. It exists so a
74
+ compromised VM can only drive this profile, not your real browser.
75
+
76
+ In that profile, install the extension from the Chrome Web Store:
77
+
78
+ ```
79
+ https://chromewebstore.google.com/detail/playwriter-mcp/jfeammnjpkecdekppnclgkkffahnhfhe
80
+ ```
81
+
82
+ Pin the extension to the toolbar. You click its icon once per tab that you
83
+ want the VM to automate.
84
+
85
+ ### 4. Enable ssh-agent (from elevated PowerShell, once)
86
+
87
+ The scheduled tasks run non-interactively. The ssh-agent service holds
88
+ your decrypted key so the tunnel can authenticate without prompting.
89
+
90
+ ```powershell
91
+ Set-Service ssh-agent -StartupType Automatic
92
+ Start-Service ssh-agent
93
+ ```
94
+
95
+ Add your key once, from any PowerShell. The command prompts for the
96
+ passphrase:
97
+
98
+ ```powershell
99
+ ssh-add $env:USERPROFILE\.ssh\id_ed25519
100
+ ```
101
+
102
+ Windows OpenSSH keeps the key in `HKLM\SOFTWARE\OpenSSH\Agent\Keys`,
103
+ encrypted with the Data Protection API (DPAPI), so it survives reboots.
104
+
105
+ ### 5. Drop the bridge files in place
106
+
107
+ Copy every file from the plugin into `%USERPROFILE%\playwriter-bridge\`:
108
+
109
+ - `launcher-src.cs`
110
+ - `build-launcher.ps1`
111
+ - `relay.ps1`
112
+ - `tunnel.ps1`
113
+ - `opener.ps1`
114
+ - `opener.js`
115
+ - `register-tasks.ps1`
116
+ - `stop.ps1`
117
+ - `doctor.ps1`
118
+
119
+ `register-tasks.ps1` refuses to run when `relay.ps1`, `tunnel.ps1`,
120
+ `opener.ps1` or `opener.js` is missing from this directory.
121
+
122
+ They live in the plugin at `plugins/acstack/assets/playwriter-bridge/`.
123
+ The same directory holds `vm-free-port.sh`, which belongs on the VM. You
124
+ do not need that file on Windows.
125
+
126
+ ### 6. Build launcher.exe (one-time)
127
+
128
+ ```powershell
129
+ cd $env:USERPROFILE\playwriter-bridge
130
+ .\build-launcher.ps1
131
+ ```
132
+
133
+ This compiles `launcher-src.cs` to `launcher.exe` in the same directory. It
134
+ uses PowerShell's built-in `Add-Type`. You install no SDK, and you need no
135
+ admin rights.
136
+
137
+ The output must be a `WindowsApplication`, not a console application. The
138
+ shipped `build-launcher.ps1` sets that correctly. It also references
139
+ `System.Management`, because the launcher walks the process tree through
140
+ WMI on shutdown.
141
+
142
+ ### 7. Pair VM access
143
+
144
+ On the VM, append your Windows public key to `~/.ssh/authorized_keys`.
145
+ Confirm from the laptop:
146
+
147
+ ```powershell
148
+ ssh -o BatchMode=yes <user>@<vm> true
149
+ ```
150
+
151
+ If that fails, produce a handoff block per [`HANDOFF.md`](HANDOFF.md). Ask
152
+ the VM-side agent to fix `authorized_keys`.
153
+
154
+ ### 8. Register the scheduled tasks
155
+
156
+ ```powershell
157
+ .\register-tasks.ps1 -VMs @("vm1.example.com", "vm2.internal")
158
+ ```
159
+
160
+ Pass every VM you want a tunnel to. Add `-TunnelUser dev` if your SSH
161
+ username on the VMs is not the same as your Windows username.
162
+
163
+ The script registers four kinds of task under `LogonType Interactive` with
164
+ `RunLevel Limited`. Each action invokes `launcher.exe`.
165
+
166
+ | Task | Runs |
167
+ | --- | --- |
168
+ | `Playwriter-Relay` | `relay.ps1`, one only |
169
+ | `Playwriter-Opener` | `opener.ps1`, one only |
170
+ | `Playwriter-Tunnel-VM<N>` | `tunnel.ps1 -TargetHost <vm>`, one per VM |
171
+ | `Playwriter-OpenerTunnel-VM<N>` | `tunnel.ps1 -TargetHost <vm> -Port 19989` |
172
+
173
+ The task restart policy is a small backstop only (2 retries, 5 min apart) –
174
+ the scripts themselves own resilience.
175
+
176
+ Fresh installs work from a regular PowerShell. `Register-ScheduledTask`
177
+ fails with `Access is denied` when you replace tasks that you registered
178
+ earlier from an elevated shell. Re-run the script from an elevated
179
+ PowerShell in that case. Elevation is a property of the earlier
180
+ registration, not of the script.
181
+
182
+ To validate a change from a checkout, deploy it first:
183
+
184
+ ```powershell
185
+ .\sync.ps1 -PluginRoot <checkout>
186
+ .\register-tasks.ps1 -VMs @("vm1.example.com")
187
+ ```
188
+
189
+ `register-tasks.ps1` reads `opener.ps1` and `opener.js` from the deploy
190
+ directory, never from the checkout. Run it before `sync.ps1` and it validates
191
+ the files still deployed rather than your change.
192
+
193
+ All tasks run at user logon from now on. Logs land in
194
+ `%LOCALAPPDATA%\playwriter-bridge\`.
195
+
196
+ ## Routine use
197
+
198
+ ### Verify the bridge is healthy
199
+
200
+ ```powershell
201
+ .\doctor.ps1
202
+ ```
203
+
204
+ `doctor.ps1` reports nine checks. It exits 0 when every check is green, and
205
+ 1 when any check is red. When a red row implies VM-side action,
206
+ `doctor.ps1` prints a ready-to-paste handoff block.
207
+
208
+ ### Stop the bridge
209
+
210
+ ```powershell
211
+ .\stop.ps1
212
+ ```
213
+
214
+ `stop.ps1` stops all `Playwriter-*` tasks, which covers the opener. It kills
215
+ lingering bridge processes. It then checks that port 19988 and port 19989 are
216
+ both free, and prints a verdict line.
217
+
218
+ ### Restart the tunnels
219
+
220
+ ```powershell
221
+ .\stop.ps1
222
+ Start-ScheduledTask -TaskName 'Playwriter-Relay'
223
+ Start-ScheduledTask -TaskName 'Playwriter-Opener'
224
+ Get-ScheduledTask -TaskName 'Playwriter-Tunnel-*','Playwriter-OpenerTunnel-*' |
225
+ ForEach-Object { Start-ScheduledTask -TaskName $_.TaskName }
226
+ .\doctor.ps1
227
+ ```
228
+
229
+ Do not restart one tunnel with a bare `Stop-ScheduledTask` and
230
+ `Start-ScheduledTask`. Task Scheduler stops the task, not the process tree
231
+ under it.
232
+
233
+ The old supervisor and its `ssh.exe` stay alive, still holding the reverse
234
+ forward, so the instance you just started never binds the port. Meanwhile
235
+ `/health` answers 200 off the orphan, so the bridge looks healthy and drives
236
+ nothing.
237
+
238
+ Measured on `Playwriter-Tunnel-VM1` during the opener work (August 2026).
239
+ Supervisor 38296 survived the stop. Its `ssh.exe` 5108 kept `-R 19989` open,
240
+ so the new instance 36408 never got the port.
241
+
242
+ `stop.ps1` is the only stop that reaps the tree. Reach for it even when only
243
+ one tunnel needs restarting. It stops every `Playwriter-*` task, which is why
244
+ the recipe starts them all again.
245
+
246
+ ### Updates (automatic)
247
+
248
+ After initial setup you do not re-copy scripts by hand. The ACStack
249
+ SessionStart hook runs `sync.ps1` on each Claude Code launch. `sync.ps1`
250
+ compares the deployed scripts in `%USERPROFILE%\playwriter-bridge\` against
251
+ the plugin cache by content hash.
252
+
253
+ It copies every script that changed. It rebuilds `launcher.exe` when the
254
+ launcher source changed. It restarts only the affected tasks.
255
+
256
+ A plugin update therefore reaches the running bridge on the next launch.
257
+ Each changed task costs at most one brief reconnect.
258
+
259
+ The whole SessionStart hook is a `bash` script. Git for Windows provides
260
+ `bash`. Put `bash` on `PATH`, or the hook never runs and auto-sync never
261
+ fires.
262
+
263
+ When `bash` is missing, re-copy the changed files per the setup step.
264
+ Then restart the tasks.
265
+
266
+ ### Per-tab activation
267
+
268
+ In Chrome, open the Playwriter Dev profile. Click the Playwriter toolbar
269
+ icon on each tab you want the VM to automate.
270
+
271
+ Playwriter attaches per tab. It cannot attach to `chrome://` and `about:`
272
+ pages.
273
+
274
+ ## The document opener
275
+
276
+ The opener shows a page from a VM in this laptop's default browser. It is a
277
+ second service, not part of the bridge.
278
+
279
+ The bridge carries CDP to a Chrome the extension drives. The opener takes a
280
+ document and opens a tab. See `docs/adr/0006-document-opener-service.md` for
281
+ why these stay apart.
282
+
283
+ Two tasks run it:
284
+
285
+ - `Playwriter-Opener` runs `opener.ps1`. That script supervises `opener.js`
286
+ on Node, restarts it when it exits, and logs to
287
+ `%LOCALAPPDATA%\playwriter-bridge\opener-supervisor.log`. `opener.js`
288
+ listens on `127.0.0.1:19989`.
289
+
290
+ - `Playwriter-OpenerTunnel-VM<N>` runs
291
+ `tunnel.ps1 -TargetHost <vm> -Port 19989`. Each VM gets its own.
292
+
293
+ The opener never shares the bridge's connection, because
294
+ `ExitOnForwardFailure=yes` makes one wedged port fail the whole `ssh`.
295
+ A stuck 19989 would take the bridge down with it.
296
+
297
+ ### The opener token
298
+
299
+ Every request from a VM carries a shared secret called the opener token.
300
+ `register-tasks.ps1` generates it and stores it at
301
+ `%LOCALAPPDATA%\playwriter-bridge\opener.token`. It then pushes the token over
302
+ `ssh` to `~/.config/acstack/opener.token` on each VM.
303
+
304
+ The token is not optional. The reverse tunnel makes 19989 reachable by
305
+ anything running on the VM, and this service puts pages on your screen.
306
+ Binding to loopback, meaning an address only this laptop can reach, does not
307
+ scope that.
308
+
309
+ `register-tasks.ps1` warns and continues when a push fails. Copy the file to
310
+ that VM by hand in this case.
311
+
312
+ ### What doctor.ps1 reports
313
+
314
+ Three of the nine rows cover the opener:
315
+
316
+ | Row | Green when |
317
+ | --- | --- |
318
+ | `Opener listening on 19989` | a process listens on `127.0.0.1:19989` |
319
+ | `Opener token` | the token file exists on this laptop |
320
+ | `Opener /health` | `opener.js` answers HTTP 200 |
321
+
322
+ The token row reports only this laptop's copy. It cannot see whether a VM
323
+ holds the same one. A VM that gets HTTP 401 has a stale copy, so re-run
324
+ `register-tasks.ps1` to push it again.
325
+
326
+ `stop.ps1` covers the opener too. It stops every `Playwriter-*` task, and it
327
+ checks 19989 alongside 19988.
328
+
329
+ ## Resilience behaviour
330
+
331
+ `tunnel.ps1` sorts an ssh failure into three kinds and reacts to each:
332
+
333
+ - **Transient** – connection refused, timed out, unreachable, DNS failure,
334
+ TCP reset under a live session, or a Win32 connect `Unknown error`. The
335
+ tunnel backs off exponentially, 5 → 10 → 20 → 40 → 80 → 120 s cap. The
336
+ backoff resets when a run lasts at least 30 s.
337
+
338
+ A dropped long-lived session lands here, so the tunnel reconnects on
339
+ its own. Laptop sleep, a wifi roam and a VPN flap all drop a session
340
+ that way.
341
+
342
+ - **Stuck remote port**
343
+ (`remote port forwarding failed for listen port 19988`).
344
+
345
+ The dropped session left an orphaned forward channel, and the VM still
346
+ holds the port through it. `tunnel.ps1` retries this one forever. It
347
+ waits 30 s, then doubles the wait to a 120 s cap. Those waits outlast
348
+ the VM's `sshd` reap window, so the bridge heals itself without a
349
+ restart.
350
+
351
+ - **Auth or host-key failure** – `tunnel.ps1` writes the marker and exits
352
+ at once. These failures need user action. Retrying only floods the log.
353
+
354
+ A port held past 8 consecutive stuck attempts, about 11 min, needs a human.
355
+ The holder is not `sshd`, or the VM has keepalive turned off. `tunnel.ps1`
356
+ writes `tunnel-<host>[-<port>].stuck` for `doctor.ps1` to report. It keeps
357
+ retrying underneath it.
358
+
359
+ Free the port on the VM and the next attempt binds. `tunnel.ps1` removes
360
+ the marker once that forward stays up for 30 s.
361
+
362
+ `doctor.ps1` does not wait for the marker. It turns the `Tunnel logs` row
363
+ amber from the first `stuck-retry` line in `tunnel-*.log`, inside a 5 minute
364
+ window. That is minutes before any marker exists.
365
+
366
+ An auth failure or a host-key failure makes `tunnel.ps1` exit and lands the
367
+ task in `Ready`. The Scheduler backstop then retries twice, 5 min apart.
368
+ `doctor.ps1` reports which host needs attention. Neither a recoverable
369
+ network drop nor a stuck port reaches that point: the tunnel loop keeps
370
+ working both.
371
+
372
+ ## How console windows stay hidden
373
+
374
+ `LogonType Interactive` allocates a desktop session. A PowerShell task that
375
+ points directly at `powershell.exe` therefore gets a visible console window
376
+ at logon. Two in-process tricks do not solve this on modern Windows:
377
+
378
+ - `-WindowStyle Hidden` – unreliable. The window still appears on the
379
+ taskbar before it hides.
380
+
381
+ - `Add-Type` with `ShowWindow(GetConsoleWindow(), SW_HIDE)` – this works on
382
+ classic `conhost`. It fails on Windows 11 22H2+, where Windows Terminal is
383
+ the default terminal host. There, `GetConsoleWindow()` returns a ConPTY
384
+ proxy handle, and `ShowWindow` on that handle does nothing to the Windows
385
+ Terminal window.
386
+
387
+ The fix is `launcher.exe`, a small C# `WindowsApplication`. It starts the
388
+ child with `CreateNoWindow = true`, so `CREATE_NO_WINDOW` propagates and
389
+ Windows attaches no console.
390
+
391
+ The launcher also waits for the child. It propagates the child's exit code.
392
+ When it shuts down on its own it kills the descendant process tree through
393
+ WMI.
394
+
395
+ That last part does not fire under `Stop-ScheduledTask`, measured twice. Task
396
+ Scheduler force-terminates `launcher.exe`, so the `ProcessExit` handler never
397
+ runs, the WMI kill never happens, and the children outlive the task.
398
+
399
+ `stop.ps1`, `sync.ps1` and `register-tasks.ps1` each reap the tree themselves
400
+ for that reason. Anything you stop by hand has to go through `stop.ps1` too.
401
+
402
+ The plugin ships the launcher as source on purpose, not as a prebuilt
403
+ binary. Each user compiles their own, so no unsigned third-party `.exe`
404
+ reaches the machine.
405
+
406
+ The bridge stores no password anywhere. The tasks run as your user's SID.
407
+ Windows puts the ssh-agent named pipe behind an access control list for that
408
+ SID, so the tasks can reach it.
409
+
410
+ ## Troubleshooting
411
+
412
+ `doctor.ps1` covers most issues. The items below cover the cases it does not
413
+ report.
414
+
415
+ - **`Empty reply from server`** on `curl /json/version` – the relay is up
416
+ but the extension has not attached to any tab yet. Click the Playwriter
417
+ icon on a normal web page. This is harmless, and `doctor.ps1` reports it
418
+ as amber, not red.
419
+
420
+ - **`Register-ScheduledTask : Access is denied`** – the tasks already
421
+ exist, and you registered them from an elevated PowerShell. A non-admin
422
+ shell cannot replace them.
423
+
424
+ Right-click PowerShell and choose Run as Administrator. Then retry.
425
+ Fresh installs do not need admin.
426
+
427
+ - **Console windows appear at logon and stay visible** – the scheduled
428
+ task action points at `powershell.exe` directly instead of at
429
+ `launcher.exe`. Inspect one task:
430
+
431
+ ```powershell
432
+ (Get-ScheduledTask Playwriter-Relay).Actions | Format-List Execute, Arguments
433
+ ```
434
+
435
+ `Execute` should end in `launcher.exe`. If it ends in `powershell.exe`,
436
+ someone registered the tasks before `launcher.exe` was in place.
437
+
438
+ Re-run `build-launcher.ps1`, then `register-tasks.ps1`. Use an elevated
439
+ PowerShell if the tasks already exist.
440
+
441
+ - **Tasks show `LastTaskResult: 267011` and `LastRunTime: 1999`** (epoch) –
442
+ the task is ready, but Windows never launched it. The most common cause
443
+ on a domain-joined laptop is a task under `LogonType S4U` without
444
+ reachable Kerberos infrastructure at logon. The shipped
445
+ `register-tasks.ps1` uses `LogonType Interactive` to avoid this.
446
+
447
+ When you see this result, check that the registered tasks are
448
+ Interactive. Re-register them if they are not:
449
+
450
+ ```powershell
451
+ Get-ScheduledTask Playwriter-* |
452
+ Select-Object TaskName, @{n='LogonType';e={$_.Principal.LogonType}}
453
+ ```
454
+
455
+ - **Passphrase prompted at every boot** – the ssh-agent service does not
456
+ start automatically. Check `Get-Service ssh-agent`. If `StartType` is not
457
+ `Automatic`, re-run step 4 from an elevated shell.
458
+
459
+ - **Endpoint security flags a scheduled task on first run** – this happens
460
+ once on some endpoints. Confirm the task is one of the `Playwriter-*`
461
+ tasks you just registered. Then allowlist it per your local procedure.
462
+
463
+ - **Endpoint security logs each reconnect on a public-IP tunnel** – some
464
+ endpoint products log an event each time you spawn `ssh.exe` with a
465
+ reverse forward. They log the event and never block it, and they stay
466
+ quiet for RFC1918 targets. That log is a detection record, not a
467
+ mitigation.
468
+
469
+ A steady-state bridge produces no further events. If your endpoint
470
+ product starts *blocking* the ssh spawn rather than logging it,
471
+ escalate to whoever owns endpoint policy at your site. Ask them for a
472
+ behavioural exclusion, or one scoped to the process arguments
473
+ `ssh.exe -R 19988:127.0.0.1:19988`.
474
+
475
+ - **Endpoint security logs on `build-launcher.ps1`** – the
476
+ `Add-Type -OutputAssembly` call invokes `csc.exe` from the .NET Framework
477
+ to compile `launcher-src.cs`. Some endpoint products flag any `csc.exe`
478
+ that PowerShell spawns, because malicious PowerShell often compiles
479
+ payloads at runtime. One alert per build is normal in that environment.
480
+
481
+ The built `launcher.exe` runs clean from then on. If that alert
482
+ disrupts your site, the alternative is to ship a prebuilt
483
+ `launcher.exe` with a checksum and skip `csc.exe`. Raise that change
484
+ with the plugin maintainer rather than patching locally.
485
+
486
+ - **`tunnel-<host>.stuck` marker present** – `tunnel.ps1` saw a fatal
487
+ classification and exited. Read the marker file for the reason and the
488
+ fix. Running `doctor.ps1` also prints this verbatim, and emits the right
489
+ handoff block.
490
+
491
+ ## Cross-device handoffs
492
+
493
+ Some steps need VM-side action – fix `authorized_keys`, free a stuck port,
494
+ or check host keys. Format any such request using
495
+ [`HANDOFF.md`](HANDOFF.md).
496
+
497
+ The `doctor.ps1` script does this automatically for its own red rows. When
498
+ you need a handoff block by hand, copy the shape verbatim.
499
+
500
+ ## Security notes
501
+
502
+ - **Dedicated Chrome profile** with no sensitive logins. VM compromise means
503
+ an attacker on the VM can drive the attached profile through the bridge.
504
+ Limit the damage by keeping that profile disposable.
505
+
506
+ - **Scheduled tasks run with `RunLevel Limited`.** They cannot elevate,
507
+ so a compromised relay or tunnel process has only the normal user's
508
+ rights.
509
+
510
+ - **Optional token authentication.** Playwriter supports `--token <secret>`
511
+ on `serve` and a matching header on CDP clients. Enable it if you want a
512
+ second control on top of the localhost-only binding.
513
+
514
+ ## What the bridge deliberately does not do
515
+
516
+ - **No `--remote-debugging-port` on your real Chrome.** The bridge exists
517
+ so that the extension API drives Chrome instead. Do not "simplify" the
518
+ design by opening a debug port.
519
+
520
+ - **No piggybacking on an editor's SSH session.** Win32-OpenSSH does not
521
+ implement `ControlMaster`. The Git-for-Windows `ssh` implements it, but
522
+ breaks when an editor pipes `cmd.exe` around it. Dedicated
523
+ scheduled-task tunnels stay independent.
524
+
525
+ - **No relay on the VM.** The Playwriter extension hard-codes
526
+ `localhost`. The relay must run next to Chrome.
@@ -0,0 +1,121 @@
1
+ ---
2
+ name: bridge
3
+ description: Set up, diagnose, and operate the Playwriter bridge – the reverse-SSH setup that lets a coding agent on a Linux VM drive a real Chrome browser on a user's Windows laptop. Any bridge work spans two devices, so this skill routes you to the right platform-specific runbook and establishes the handoff convention for cross-device work. ALWAYS invoke before touching bridge scripts, diagnosing connection issues to the user's browser, or responding to questions about the CDP tunnel on port 19988.
4
+ ---
5
+
6
+ # Playwriter bridge
7
+
8
+ ## What this skill covers
9
+
10
+ The Playwriter bridge is a cross-device setup.
11
+
12
+ A Node relay + Chrome extension run on the user's Windows laptop. A
13
+ reverse SSH tunnel makes that relay reachable at `127.0.0.1:19988` on one
14
+ or more Linux VMs. A coding agent on the VM uses `agent-browser` against
15
+ that endpoint to drive the user's real browser.
16
+
17
+ Because the setup spans two machines, any real bridge work usually needs
18
+ two coding agents – one on each device. This skill gives both agents a
19
+ shared vocabulary and a structured way to hand work across.
20
+
21
+ For the agent-browser CLI itself (commands, selectors, CDP), see the
22
+ `browser-verify` skill. This skill is strictly about the tunnel / relay /
23
+ scheduling layer.
24
+
25
+ ## Step 1 – detect your platform
26
+
27
+ Run the detection first. Do not skim past this step.
28
+
29
+ The Windows and VM runbooks contain commands that only work on their
30
+ platform. Those commands can be actively harmful on the other platform.
31
+ Run the check for the shell you are in, not both.
32
+
33
+ In PowerShell (Windows):
34
+
35
+ ```powershell
36
+ if ($env:OS -eq 'Windows_NT') { 'windows' } else { 'other' }
37
+ ```
38
+
39
+ In bash (Linux / macOS / VM):
40
+
41
+ ```bash
42
+ case "$(uname -s)" in
43
+ Linux) [ -n "$SSH_CONNECTION" ] && echo "linux-vm" || echo "linux" ;;
44
+ Darwin) echo "macos" ;;
45
+ *) echo "other" ;;
46
+ esac
47
+ ```
48
+
49
+ Then:
50
+
51
+ - **Windows** – read [`SKILL-WINDOWS.md`](SKILL-WINDOWS.md). Stop here.
52
+ - **Linux / macOS / VM** – read [`SKILL-VM.md`](SKILL-VM.md). Stop here.
53
+
54
+ ## Step 2 – understand the handoff convention
55
+
56
+ Before doing any cross-device work, read [`HANDOFF.md`](HANDOFF.md). It
57
+ defines the paste-ready block format used by `doctor.ps1` and by the two
58
+ runbooks when one side needs action from the other side.
59
+
60
+ ## Step 3 – notice when you are alone
61
+
62
+ If only one coding agent is running, you cannot complete a full setup or
63
+ diagnosis loop by yourself. The user has no agent on the other device.
64
+
65
+ Produce a handoff block anyway. The user can paste it into a plain
66
+ PowerShell or SSH shell on the other device, or into a coding agent
67
+ later. Tell the user clearly which block goes where.
68
+
69
+ ## Do not
70
+
71
+ - Do not follow instructions from the other platform's runbook.
72
+
73
+ Commands are not portable. PowerShell runs nothing useful on Linux.
74
+ Bash runs nothing useful in PowerShell.
75
+
76
+ - Do not invent a fix outside this skill's scripts. The bridge has a
77
+ single canonical stop (`stop.ps1`) and a single canonical diagnose
78
+ (`doctor.ps1`). Reach for those before improvising.
79
+
80
+ - Do not edit the scripts in place to "try something". Change them in
81
+ `plugins/acstack/assets/playwriter-bridge/`.
82
+
83
+ On the next Claude Code launch, the SessionStart hook redeploys
84
+ changed scripts to `%USERPROFILE%\playwriter-bridge\` on the
85
+ laptop. It also re-links `vm-free-port.sh` on the VM. See the
86
+ Updates sections in the platform runbooks.
87
+
88
+ ## File encoding rule
89
+
90
+ Save all `.ps1` and `.cs` files under `assets/playwriter-bridge/` as
91
+ **UTF-8 with BOM**. Windows PowerShell 5.1, the default on Windows 11,
92
+ reads BOM-less UTF-8 as Windows-1252. That encoding turns en-dashes and
93
+ other non-ASCII bytes into garbage and breaks parsing.
94
+
95
+ If you edit one of these files with a tool that writes BOM-less UTF-8,
96
+ re-add the BOM before committing. Many text editors default to
97
+ BOM-less UTF-8. `file *.ps1 *.cs` in the directory should report each
98
+ one as "UTF-8 Unicode (with BOM)".
99
+
100
+ ## Files this skill governs
101
+
102
+ Under `plugins/acstack/assets/playwriter-bridge/`:
103
+
104
+ - `launcher-src.cs`, `build-launcher.ps1` – launcher shim (hides console,
105
+ owns the child process tree)
106
+
107
+ - `relay.ps1`, `tunnel.ps1` – long-running bridge scripts
108
+ - `register-tasks.ps1` – scheduled-task registration
109
+ - `stop.ps1` – canonical stop
110
+ - `doctor.ps1` – Windows automated diagnosis
111
+ - `sync.ps1` – Windows auto-deploy: reconciles the deployed scripts with
112
+ the plugin cache (invoked by the SessionStart hook)
113
+
114
+ - `vm-free-port.sh` – VM-side port cleanup with guardrails
115
+
116
+ The user installs the scripts once into `%USERPROFILE%\playwriter-bridge\`
117
+ (Windows) during setup. After that, the SessionStart hook keeps them
118
+ current with the plugin cache automatically. It uses `sync.ps1` on
119
+ Windows, and a symlink for `vm-free-port.sh` on the VM.
120
+
121
+ Plugin updates then reach the running bridge without a manual re-copy.