ptywright 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.
- package/LICENSE +201 -0
- package/README.md +188 -0
- package/bin/ptywright +4 -0
- package/package.json +76 -0
- package/schemas/ptywright-script.schema.json +297 -0
- package/skills/ptywright-testing/SKILL.md +102 -0
- package/src/cli.ts +414 -0
- package/src/generator/doc_parser.ts +341 -0
- package/src/generator/generate.ts +161 -0
- package/src/generator/index.ts +10 -0
- package/src/generator/script_generator.ts +209 -0
- package/src/generator/step_extractor.ts +397 -0
- package/src/index.ts +16 -0
- package/src/mcp/http_server.ts +174 -0
- package/src/mcp/script_recording.ts +238 -0
- package/src/mcp/server.ts +1348 -0
- package/src/pty/bun_pty_adapter.ts +34 -0
- package/src/pty/bun_terminal_adapter.ts +149 -0
- package/src/pty/pty_adapter.ts +31 -0
- package/src/script/dsl.ts +188 -0
- package/src/script/module.ts +43 -0
- package/src/script/path.ts +151 -0
- package/src/script/run.ts +108 -0
- package/src/script/run_all.ts +229 -0
- package/src/script/runner.ts +983 -0
- package/src/script/schema.ts +237 -0
- package/src/script/steps/assert_snapshot_equals.ts +21 -0
- package/src/script/steps/index.ts +2 -0
- package/src/script/suite_report.ts +626 -0
- package/src/session/session_manager.ts +145 -0
- package/src/session/terminal_session.ts +473 -0
- package/src/terminal/ansi.ts +142 -0
- package/src/terminal/keys.ts +180 -0
- package/src/terminal/mask.ts +70 -0
- package/src/terminal/mouse.ts +75 -0
- package/src/terminal/snapshot.ts +196 -0
- package/src/terminal/style.ts +121 -0
- package/src/terminal/view.ts +49 -0
- package/src/trace/asciicast.ts +20 -0
- package/src/trace/asciinema_player_assets.ts +44 -0
- package/src/trace/cast_to_txt.ts +116 -0
- package/src/trace/recorder.ts +110 -0
- package/src/trace/report.ts +2092 -0
- package/src/types.ts +86 -0
- package/src/util/hash.ts +8 -0
- package/src/util/sleep.ts +5 -0
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"title": "ptywright Script",
|
|
4
|
+
"type": "object",
|
|
5
|
+
"additionalProperties": false,
|
|
6
|
+
"required": ["launch", "steps"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"$schema": { "type": "string" },
|
|
9
|
+
"name": { "type": "string" },
|
|
10
|
+
"artifactsDir": { "type": "string" },
|
|
11
|
+
"launch": { "$ref": "#/$defs/launch" },
|
|
12
|
+
"trace": { "$ref": "#/$defs/trace" },
|
|
13
|
+
"steps": {
|
|
14
|
+
"type": "array",
|
|
15
|
+
"minItems": 1,
|
|
16
|
+
"items": { "$ref": "#/$defs/step" }
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"$defs": {
|
|
20
|
+
"textMaskRule": {
|
|
21
|
+
"type": "object",
|
|
22
|
+
"additionalProperties": false,
|
|
23
|
+
"required": ["regex"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"regex": { "type": "string", "minLength": 1 },
|
|
26
|
+
"flags": { "type": "string" },
|
|
27
|
+
"replacement": { "type": "string" },
|
|
28
|
+
"preserveLength": { "type": "boolean" }
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"launch": {
|
|
32
|
+
"type": "object",
|
|
33
|
+
"additionalProperties": false,
|
|
34
|
+
"required": ["command"],
|
|
35
|
+
"properties": {
|
|
36
|
+
"command": { "type": "string", "minLength": 1 },
|
|
37
|
+
"args": { "type": "array", "items": { "type": "string" } },
|
|
38
|
+
"cwd": { "type": "string" },
|
|
39
|
+
"env": {
|
|
40
|
+
"type": "object",
|
|
41
|
+
"additionalProperties": { "type": "string" }
|
|
42
|
+
},
|
|
43
|
+
"cols": { "type": "integer", "minimum": 1 },
|
|
44
|
+
"rows": { "type": "integer", "minimum": 1 },
|
|
45
|
+
"name": { "type": "string" }
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"trace": {
|
|
49
|
+
"type": "object",
|
|
50
|
+
"additionalProperties": false,
|
|
51
|
+
"properties": {
|
|
52
|
+
"saveCast": { "type": "boolean" },
|
|
53
|
+
"saveReport": { "type": "boolean" },
|
|
54
|
+
"castPath": { "type": "string" },
|
|
55
|
+
"reportPath": { "type": "string" },
|
|
56
|
+
"reportScope": { "type": "string", "enum": ["visible", "buffer"] },
|
|
57
|
+
"reportMaxFrames": { "type": "integer", "minimum": 1 }
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"step": {
|
|
61
|
+
"oneOf": [
|
|
62
|
+
{ "$ref": "#/$defs/stepSendText" },
|
|
63
|
+
{ "$ref": "#/$defs/stepPressKey" },
|
|
64
|
+
{ "$ref": "#/$defs/stepSendMouse" },
|
|
65
|
+
{ "$ref": "#/$defs/stepResize" },
|
|
66
|
+
{ "$ref": "#/$defs/stepMark" },
|
|
67
|
+
{ "$ref": "#/$defs/stepSleep" },
|
|
68
|
+
{ "$ref": "#/$defs/stepWaitForText" },
|
|
69
|
+
{ "$ref": "#/$defs/stepWaitForStableScreen" },
|
|
70
|
+
{ "$ref": "#/$defs/stepWaitForExit" },
|
|
71
|
+
{ "$ref": "#/$defs/stepExpectMeta" },
|
|
72
|
+
{ "$ref": "#/$defs/stepSnapshot" },
|
|
73
|
+
{ "$ref": "#/$defs/stepExpect" },
|
|
74
|
+
{ "$ref": "#/$defs/stepAssert" },
|
|
75
|
+
{ "$ref": "#/$defs/stepAssertSemantic" },
|
|
76
|
+
{ "$ref": "#/$defs/stepExpectGolden" },
|
|
77
|
+
{ "$ref": "#/$defs/stepCustom" }
|
|
78
|
+
]
|
|
79
|
+
},
|
|
80
|
+
"stepSendText": {
|
|
81
|
+
"type": "object",
|
|
82
|
+
"additionalProperties": false,
|
|
83
|
+
"required": ["type", "text"],
|
|
84
|
+
"properties": {
|
|
85
|
+
"type": { "const": "sendText" },
|
|
86
|
+
"text": { "type": "string" },
|
|
87
|
+
"enter": { "type": "boolean" }
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"stepPressKey": {
|
|
91
|
+
"type": "object",
|
|
92
|
+
"additionalProperties": false,
|
|
93
|
+
"required": ["type", "key"],
|
|
94
|
+
"properties": {
|
|
95
|
+
"type": { "const": "pressKey" },
|
|
96
|
+
"key": { "type": "string", "minLength": 1 }
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
"stepSendMouse": {
|
|
100
|
+
"type": "object",
|
|
101
|
+
"additionalProperties": false,
|
|
102
|
+
"required": ["type", "action", "x", "y"],
|
|
103
|
+
"properties": {
|
|
104
|
+
"type": { "const": "sendMouse" },
|
|
105
|
+
"action": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"enum": ["down", "up", "move", "click", "scroll_up", "scroll_down"]
|
|
108
|
+
},
|
|
109
|
+
"x": { "type": "integer" },
|
|
110
|
+
"y": { "type": "integer" },
|
|
111
|
+
"button": { "type": "string", "enum": ["left", "middle", "right"] },
|
|
112
|
+
"shift": { "type": "boolean" },
|
|
113
|
+
"alt": { "type": "boolean" },
|
|
114
|
+
"ctrl": { "type": "boolean" }
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
"stepResize": {
|
|
118
|
+
"type": "object",
|
|
119
|
+
"additionalProperties": false,
|
|
120
|
+
"required": ["type", "cols", "rows"],
|
|
121
|
+
"properties": {
|
|
122
|
+
"type": { "const": "resize" },
|
|
123
|
+
"cols": { "type": "integer", "minimum": 1 },
|
|
124
|
+
"rows": { "type": "integer", "minimum": 1 }
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"stepMark": {
|
|
128
|
+
"type": "object",
|
|
129
|
+
"additionalProperties": false,
|
|
130
|
+
"required": ["type"],
|
|
131
|
+
"properties": {
|
|
132
|
+
"type": { "const": "mark" },
|
|
133
|
+
"label": { "type": "string" }
|
|
134
|
+
}
|
|
135
|
+
},
|
|
136
|
+
"stepSleep": {
|
|
137
|
+
"type": "object",
|
|
138
|
+
"additionalProperties": false,
|
|
139
|
+
"required": ["type", "ms"],
|
|
140
|
+
"properties": {
|
|
141
|
+
"type": { "const": "sleep" },
|
|
142
|
+
"ms": { "type": "integer", "minimum": 0 }
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"stepWaitForText": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"additionalProperties": false,
|
|
148
|
+
"required": ["type"],
|
|
149
|
+
"properties": {
|
|
150
|
+
"type": { "const": "waitForText" },
|
|
151
|
+
"scope": { "type": "string", "enum": ["visible", "buffer"] },
|
|
152
|
+
"text": { "type": "string" },
|
|
153
|
+
"regex": { "type": "string" },
|
|
154
|
+
"timeoutMs": { "type": "integer", "minimum": 1 },
|
|
155
|
+
"intervalMs": { "type": "integer", "minimum": 1 }
|
|
156
|
+
},
|
|
157
|
+
"anyOf": [{ "required": ["text"] }, { "required": ["regex"] }]
|
|
158
|
+
},
|
|
159
|
+
"stepWaitForStableScreen": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"additionalProperties": false,
|
|
162
|
+
"required": ["type"],
|
|
163
|
+
"properties": {
|
|
164
|
+
"type": { "const": "waitForStableScreen" },
|
|
165
|
+
"timeoutMs": { "type": "integer", "minimum": 1 },
|
|
166
|
+
"quietMs": { "type": "integer", "minimum": 1 },
|
|
167
|
+
"intervalMs": { "type": "integer", "minimum": 1 }
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
"stepWaitForExit": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"additionalProperties": false,
|
|
173
|
+
"required": ["type"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"type": { "const": "waitForExit" },
|
|
176
|
+
"timeoutMs": { "type": "integer", "minimum": 1 },
|
|
177
|
+
"intervalMs": { "type": "integer", "minimum": 1 },
|
|
178
|
+
"exitCode": { "type": "integer" },
|
|
179
|
+
"signal": { "oneOf": [{ "type": "integer" }, { "type": "string" }] }
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
"stepExpectMeta": {
|
|
183
|
+
"type": "object",
|
|
184
|
+
"additionalProperties": false,
|
|
185
|
+
"required": ["type"],
|
|
186
|
+
"properties": {
|
|
187
|
+
"type": { "const": "expectMeta" },
|
|
188
|
+
"bufferType": { "type": "string", "enum": ["normal", "alternate"] },
|
|
189
|
+
"cols": { "type": "integer", "minimum": 1 },
|
|
190
|
+
"rows": { "type": "integer", "minimum": 1 },
|
|
191
|
+
"cursor": {
|
|
192
|
+
"type": "object",
|
|
193
|
+
"additionalProperties": false,
|
|
194
|
+
"required": ["x", "y"],
|
|
195
|
+
"properties": {
|
|
196
|
+
"x": { "type": "integer", "minimum": 1 },
|
|
197
|
+
"y": { "type": "integer", "minimum": 1 }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
"anyOf": [
|
|
202
|
+
{ "required": ["bufferType"] },
|
|
203
|
+
{ "required": ["cols"] },
|
|
204
|
+
{ "required": ["rows"] },
|
|
205
|
+
{ "required": ["cursor"] }
|
|
206
|
+
]
|
|
207
|
+
},
|
|
208
|
+
"stepSnapshot": {
|
|
209
|
+
"type": "object",
|
|
210
|
+
"additionalProperties": false,
|
|
211
|
+
"required": ["type", "kind"],
|
|
212
|
+
"properties": {
|
|
213
|
+
"type": { "const": "snapshot" },
|
|
214
|
+
"kind": { "type": "string", "enum": ["text", "view", "ansi", "view_ansi", "grid"] },
|
|
215
|
+
"scope": { "type": "string", "enum": ["visible", "buffer"] },
|
|
216
|
+
"trimRight": { "type": "boolean" },
|
|
217
|
+
"trimBottom": { "type": "boolean" },
|
|
218
|
+
"maxLines": { "type": "integer", "minimum": 1 },
|
|
219
|
+
"tailLines": { "type": "integer", "minimum": 1 },
|
|
220
|
+
"lineNumbers": { "type": "boolean" },
|
|
221
|
+
"includeStyles": { "type": "boolean" },
|
|
222
|
+
"mask": { "type": "array", "items": { "$ref": "#/$defs/textMaskRule" } },
|
|
223
|
+
"saveAs": { "type": "string" },
|
|
224
|
+
"saveTo": { "type": "string" }
|
|
225
|
+
},
|
|
226
|
+
"allOf": [
|
|
227
|
+
{ "not": { "required": ["maxLines", "tailLines"] } },
|
|
228
|
+
{
|
|
229
|
+
"if": { "properties": { "kind": { "const": "grid" } } },
|
|
230
|
+
"then": { "not": { "required": ["mask"] } }
|
|
231
|
+
}
|
|
232
|
+
]
|
|
233
|
+
},
|
|
234
|
+
"stepExpect": {
|
|
235
|
+
"type": "object",
|
|
236
|
+
"additionalProperties": false,
|
|
237
|
+
"required": ["type"],
|
|
238
|
+
"properties": {
|
|
239
|
+
"type": { "const": "expect" },
|
|
240
|
+
"from": { "type": "string" },
|
|
241
|
+
"equals": { "type": "string" },
|
|
242
|
+
"contains": { "type": "array", "items": { "type": "string" } },
|
|
243
|
+
"notContains": { "type": "array", "items": { "type": "string" } },
|
|
244
|
+
"regex": { "type": "string" }
|
|
245
|
+
},
|
|
246
|
+
"anyOf": [
|
|
247
|
+
{ "required": ["equals"] },
|
|
248
|
+
{ "required": ["contains"] },
|
|
249
|
+
{ "required": ["notContains"] },
|
|
250
|
+
{ "required": ["regex"] }
|
|
251
|
+
]
|
|
252
|
+
},
|
|
253
|
+
"stepAssert": {
|
|
254
|
+
"type": "object",
|
|
255
|
+
"additionalProperties": false,
|
|
256
|
+
"required": ["type"],
|
|
257
|
+
"properties": {
|
|
258
|
+
"type": { "const": "assert" },
|
|
259
|
+
"scope": { "type": "string", "enum": ["visible", "buffer"] },
|
|
260
|
+
"text": { "type": "string" },
|
|
261
|
+
"regex": { "type": "string" },
|
|
262
|
+
"description": { "type": "string" }
|
|
263
|
+
},
|
|
264
|
+
"anyOf": [{ "required": ["text"] }, { "required": ["regex"] }]
|
|
265
|
+
},
|
|
266
|
+
"stepAssertSemantic": {
|
|
267
|
+
"type": "object",
|
|
268
|
+
"additionalProperties": false,
|
|
269
|
+
"required": ["type", "prompt"],
|
|
270
|
+
"properties": {
|
|
271
|
+
"type": { "const": "assertSemantic" },
|
|
272
|
+
"prompt": { "type": "string", "minLength": 1 },
|
|
273
|
+
"description": { "type": "string" }
|
|
274
|
+
}
|
|
275
|
+
},
|
|
276
|
+
"stepExpectGolden": {
|
|
277
|
+
"type": "object",
|
|
278
|
+
"additionalProperties": false,
|
|
279
|
+
"required": ["type", "path"],
|
|
280
|
+
"properties": {
|
|
281
|
+
"type": { "const": "expectGolden" },
|
|
282
|
+
"from": { "type": "string" },
|
|
283
|
+
"path": { "type": "string", "minLength": 1 }
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"stepCustom": {
|
|
287
|
+
"type": "object",
|
|
288
|
+
"additionalProperties": false,
|
|
289
|
+
"required": ["type", "name"],
|
|
290
|
+
"properties": {
|
|
291
|
+
"type": { "const": "custom" },
|
|
292
|
+
"name": { "type": "string", "minLength": 1 },
|
|
293
|
+
"payload": {}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ptywright-testing
|
|
3
|
+
description: Terminal/TUI automation and regression testing using ptywright (PTY + xterm) via CLI or MCP tools. Use when you need to (1) drive a CLI/TUI app (send keys/mouse, wait, snapshot), (2) run scripted regressions (run/run-all) and review the HTML report (index.html + run.summary.json), or (3) record an interactive MCP-driven session into a replayable script with golden checkpoints.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Ptywright Testing / 使用指南
|
|
7
|
+
|
|
8
|
+
Use ptywright to run deterministic CLI/TUI regression tests with readable “terminal screenshots” and a Playwright-like HTML report.
|
|
9
|
+
|
|
10
|
+
## How to run (发布版 vs 仓库内开发)
|
|
11
|
+
|
|
12
|
+
This CLI is Bun-based (`#!/usr/bin/env bun`). For a released package, prefer `bunx`.
|
|
13
|
+
|
|
14
|
+
- **Published package (recommended):**
|
|
15
|
+
- one-off: `bunx ptywright@latest <command>`
|
|
16
|
+
- pinned: `bunx ptywright@0.1.0 <command>`
|
|
17
|
+
- global: `bun add -g ptywright` then `ptywright <command>`
|
|
18
|
+
- **Inside this repo (dev):**
|
|
19
|
+
- `bun run bin/ptywright <command>`
|
|
20
|
+
|
|
21
|
+
## Choose the interface
|
|
22
|
+
|
|
23
|
+
- **MCP tools**: best for agent-driven interactive flows (`launch_session`, `wait_for_text`, snapshots, recording).
|
|
24
|
+
- **CLI**: best for local deterministic regressions and reviewing HTML reports.
|
|
25
|
+
|
|
26
|
+
## Start the MCP server
|
|
27
|
+
|
|
28
|
+
- stdio (default):
|
|
29
|
+
- published: `bunx ptywright@latest mcp`
|
|
30
|
+
- repo: `bun run bin/ptywright mcp`
|
|
31
|
+
- restrict tools to reduce context:
|
|
32
|
+
- published: `bunx ptywright@latest mcp --caps core`
|
|
33
|
+
- repo: `bun run bin/ptywright mcp --caps core` (or `PTYWRIGHT_CAPS=core`)
|
|
34
|
+
- Streamable HTTP:
|
|
35
|
+
- published: `bunx ptywright@latest mcp-http --port 3000`
|
|
36
|
+
- repo: `bun run bin/ptywright mcp-http --port 3000`
|
|
37
|
+
|
|
38
|
+
Capabilities (`--caps` / `PTYWRIGHT_CAPS`) match MCP tools:
|
|
39
|
+
|
|
40
|
+
- `all|core|debug|script|recording` (comma/space separated)
|
|
41
|
+
|
|
42
|
+
## Run scripts (deterministic regression)
|
|
43
|
+
|
|
44
|
+
### Run the whole suite (preferred)
|
|
45
|
+
|
|
46
|
+
- CLI:
|
|
47
|
+
- published: `bunx ptywright@latest run-all --dir scripts`
|
|
48
|
+
- repo: `bun run bin/ptywright run-all --dir scripts`
|
|
49
|
+
- Output to focus on:
|
|
50
|
+
- `reportPath` (open in a browser)
|
|
51
|
+
- `summaryPath` (`run.summary.json` for agents/CI)
|
|
52
|
+
|
|
53
|
+
MCP equivalent:
|
|
54
|
+
|
|
55
|
+
- `run_all_scripts` (defaults: `dir="scripts"`, suite report in `.tmp/run-all/`)
|
|
56
|
+
- Keep MCP output small: `run_all_scripts(includeEntries="failures", maxEntries=20)`
|
|
57
|
+
|
|
58
|
+
### Run one script
|
|
59
|
+
|
|
60
|
+
- CLI:
|
|
61
|
+
- published: `bunx ptywright@latest run <file.json|file.ts> [--artifacts-dir <dir>]`
|
|
62
|
+
- repo: `bun run bin/ptywright run <file.json|file.ts> [--artifacts-dir <dir>]`
|
|
63
|
+
- MCP: `run_script(scriptPath=...)`
|
|
64
|
+
|
|
65
|
+
## Debug a failure
|
|
66
|
+
|
|
67
|
+
Script runner artifacts to check (paths are returned by CLI/MCP):
|
|
68
|
+
|
|
69
|
+
- `*.report.html` (timeline + snapshots)
|
|
70
|
+
- `*.cast` (full playback)
|
|
71
|
+
- `failure.last.view.txt` / `failure.last.txt` (last screen)
|
|
72
|
+
- `failure.error.txt` (stack trace)
|
|
73
|
+
|
|
74
|
+
Tip: for flaky waits, prefer `scope="buffer"` when the content may have scrolled into scrollback.
|
|
75
|
+
|
|
76
|
+
## Record an interactive flow (MCP)
|
|
77
|
+
|
|
78
|
+
1) `start_script_recording(name=...)`
|
|
79
|
+
2) Drive the app with normal tools:
|
|
80
|
+
- `launch_session` → `send_text` / `press_key` / `wait_for_text` / `snapshot_*`
|
|
81
|
+
3) Add golden checkpoints: `mark(label=...)`
|
|
82
|
+
4) Export: `stop_script_recording(recordingId=..., writeFiles=true)`
|
|
83
|
+
|
|
84
|
+
## All-tools smoke (recommended)
|
|
85
|
+
|
|
86
|
+
To verify ptywright MCP tool coverage without relying on external apps/network, run:
|
|
87
|
+
|
|
88
|
+
- `bun test tests/mcp_all_tools_smoke.test.ts`
|
|
89
|
+
|
|
90
|
+
This exercises `core + debug + script + recording` tools end-to-end.
|
|
91
|
+
|
|
92
|
+
## Determinism tips
|
|
93
|
+
|
|
94
|
+
- Fix terminal size (`cols/rows`) and `TERM` (`xterm-256color`) in `launch_session`.
|
|
95
|
+
- Use `wait_for_stable_screen` before assertions/snapshots to reduce flake.
|
|
96
|
+
- Use `mask` to redact timestamps, random IDs, spinners, etc.
|
|
97
|
+
- For live LLM apps: assert on stable markers/state transitions, not exact prose.
|
|
98
|
+
|
|
99
|
+
## Environment knobs
|
|
100
|
+
|
|
101
|
+
- `TUI_TEST_PTY_BACKEND=auto|bun-terminal|bun-pty`
|
|
102
|
+
- default `auto`: macOS/Linux prefers `bun-terminal`, Windows uses `bun-pty`
|