acstack 0.0.0-stage → 0.47.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.
- package/.claude-plugin/plugin.json +19 -0
- package/LICENSE +21 -0
- package/README.md +389 -2
- package/THIRD_PARTY_NOTICES +14 -0
- package/TROUBLESHOOTING.md +225 -0
- package/agents/codex/frontend-verifier.toml +352 -0
- package/agents/codex/task-checker.toml +345 -0
- package/agents/codex/task-executor.toml +157 -0
- package/agents/codex/test-writer.toml +149 -0
- package/agents/frontend-verifier.md +351 -0
- package/agents/task-checker.md +344 -0
- package/agents/task-executor.md +156 -0
- package/agents/test-writer.md +148 -0
- package/assets/playwriter-bridge/build-launcher.ps1 +32 -0
- package/assets/playwriter-bridge/doctor.ps1 +450 -0
- package/assets/playwriter-bridge/launcher-src.cs +131 -0
- package/assets/playwriter-bridge/opener.js +330 -0
- package/assets/playwriter-bridge/opener.ps1 +63 -0
- package/assets/playwriter-bridge/register-tasks.ps1 +328 -0
- package/assets/playwriter-bridge/relay.ps1 +50 -0
- package/assets/playwriter-bridge/stop.ps1 +125 -0
- package/assets/playwriter-bridge/sync.ps1 +271 -0
- package/assets/playwriter-bridge/tunnel.ps1 +325 -0
- package/assets/playwriter-bridge/vm-free-port.sh +166 -0
- package/assets/terminal-workspace/client-keybindings.md +87 -0
- package/assets/terminal-workspace/config.example.yaml +334 -0
- package/assets/terminal-workspace/setup.sh +4404 -0
- package/cli/README.md +47 -0
- package/cli/bin/acstack.js +3 -0
- package/cli/dist/index.js +25984 -0
- package/cli/dist/index.js.map +7 -0
- package/cli/package.json +59 -0
- package/docs/adr/0001-ste-writing-standard.md +16 -0
- package/docs/adr/0002-version-gate-not-auto-bump.md +35 -0
- package/docs/adr/0003-handoff-ack-command.md +19 -0
- package/docs/adr/0004-dev-setup-per-host.md +13 -0
- package/docs/adr/0005-two-orchestrators-per-host.md +25 -0
- package/docs/adr/0006-document-opener-service.md +89 -0
- package/docs/adr/0007-lead-check-reads-the-transcript.md +87 -0
- package/docs/adr/0008-cli-audits-skill-interviews.md +37 -0
- package/docs/adr/0009-verify-the-write-not-the-writer.md +51 -0
- package/docs/adr/0010-promote-dev-to-main-by-fast-forward.md +115 -0
- package/docs/adr/0011-node-header-is-the-edge-encoding.md +20 -0
- package/docs/adr/0012-node-kind-is-a-closed-set-of-five.md +31 -0
- package/docs/adr/0013-the-diagram-renderer-is-pure.md +23 -0
- package/docs/adr/0014-reading-a-node-hides-its-generated-diagram.md +29 -0
- package/docs/adr/0015-a-node-id-links-to-its-issue-through-an-anchor.md +37 -0
- package/docs/adr/0016-the-map-root-carries-no-status-of-its-own.md +21 -0
- package/docs/adr/0017-call-impeccable-copy-only-its-questions.md +13 -0
- package/docs/adr/index.md +17 -0
- package/docs/config-reference.md +572 -0
- package/docs/dev-setups.md +164 -0
- package/docs/migrate-0.8.md +28 -0
- package/docs/playwriter-bridge-windows.md +15 -0
- package/docs/terminal-workspace-keys.md +137 -0
- package/docs/terminal-workspace.md +1780 -0
- package/docs/workflows.md +309 -0
- package/hooks/ac-writing-style-reminder.sh +86 -0
- package/hooks/context-pressure.sh +583 -0
- package/hooks/hooks.json +50 -0
- package/hooks/session-start-handoff.sh +165 -0
- package/hooks/session-start.sh +214 -0
- package/orchestrator/AGENTS.md +532 -0
- package/output-styles/ac-writing-style.md +286 -0
- package/package.json +50 -4
- package/pi/agents/frontend-verifier.md +357 -0
- package/pi/agents/task-checker.md +348 -0
- package/pi/agents/task-executor.md +160 -0
- package/pi/agents/test-writer.md +154 -0
- package/pi/extension.ts +723 -0
- package/profiles/fullstack-python.yaml +29 -0
- package/profiles/node-typescript.yaml +23 -0
- package/profiles/python.yaml +24 -0
- package/skills/archive/SKILL.md +160 -0
- package/skills/bootstrap/SKILL.md +109 -0
- package/skills/bridge/HANDOFF.md +93 -0
- package/skills/bridge/SKILL-VM.md +292 -0
- package/skills/bridge/SKILL-WINDOWS.md +526 -0
- package/skills/bridge/SKILL.md +121 -0
- package/skills/browser-verify/SKILL.md +229 -0
- package/skills/code-review/SKILL.md +44 -0
- package/skills/commit/SKILL.md +106 -0
- package/skills/compact-and-continue/SKILL.md +241 -0
- package/skills/explore/SKILL.md +149 -0
- package/skills/grilling/SKILL.md +134 -0
- package/skills/handoff/SKILL.md +250 -0
- package/skills/handoff/launch-herdr.md +197 -0
- package/skills/handoff/launch-without-herdr.md +30 -0
- package/skills/handoff/lead-check.md +59 -0
- package/skills/handoff/outcomes.md +162 -0
- package/skills/handoff/running-agent.md +50 -0
- package/skills/handoff/watch.md +115 -0
- package/skills/host/SKILL.md +247 -0
- package/skills/host/detect-host.sh +297 -0
- package/skills/host/herdr.md +79 -0
- package/skills/host/install-herdr-block.sh +467 -0
- package/skills/host/install-orca.sh +512 -0
- package/skills/host/notes.md +97 -0
- package/skills/host/orca.md +160 -0
- package/skills/host/questions.md +163 -0
- package/skills/host/report.md +42 -0
- package/skills/implement/SKILL.md +230 -0
- package/skills/implement/decision-log.md +62 -0
- package/skills/implement/design-pass.md +32 -0
- package/skills/implement/relaxed-tdd.md +28 -0
- package/skills/map/SKILL.md +250 -0
- package/skills/map/frontend.md +227 -0
- package/skills/map/trackers/files.md +74 -0
- package/skills/map/trackers/github.md +180 -0
- package/skills/map/visuals.md +387 -0
- package/skills/new-worktree/SKILL.md +155 -0
- package/skills/new-worktree/detect-orchestrator.sh +328 -0
- package/skills/new-worktree/detect-orchestrator.test.sh +894 -0
- package/skills/new-worktree/herdr.md +84 -0
- package/skills/new-worktree/orca.md +101 -0
- package/skills/pre-commit-review/SKILL.md +242 -0
- package/skills/pre-commit-review/auto-fix.md +19 -0
- package/skills/pre-commit-review/interactive.md +35 -0
- package/skills/quick-fix/SKILL.md +125 -0
- package/skills/record-context/SKILL.md +106 -0
- package/skills/setup/SKILL.md +250 -0
- package/skills/setup/browser.md +123 -0
- package/skills/setup/design-review.md +97 -0
- package/skills/setup/first-run.md +114 -0
- package/skills/setup/report.md +68 -0
- package/skills/setup/row-fixes.md +196 -0
- package/skills/setup/terminal-workspace.md +153 -0
- package/skills/sync/SKILL.md +171 -0
- package/skills/teardown-worktree/SKILL.md +245 -0
- package/skills/teardown-worktree/detector.md +39 -0
- package/skills/teardown-worktree/herdr.md +113 -0
- package/skills/teardown-worktree/orca.md +61 -0
- package/skills/terminal-workspace/SKILL.md +249 -0
- package/skills/terminal-workspace/components.md +36 -0
- package/skills/terminal-workspace/config-choices.md +69 -0
- package/skills/terminal-workspace/emulator-keys.md +48 -0
- package/skills/terminal-workspace/headless-clipboard.md +175 -0
- package/skills/terminal-workspace/turn-off-and-update.md +104 -0
- package/skills/test-conventions/SKILL.md +59 -0
- package/skills/visual-docs/SKILL.md +246 -0
- package/skills/writing/SKILL.md +231 -0
- package/skills/writing/examples.md +153 -0
- 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.
|