@termwright/mcp 0.2.0 → 0.3.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/README.md +109 -28
- package/dist/bin.js +2 -1
- package/dist/bin.js.map +1 -1
- package/dist/chunk-R2N52YYH.js +629 -0
- package/dist/chunk-R2N52YYH.js.map +1 -0
- package/dist/{chunk-IPNUAUAN.js → chunk-ROMJP5D3.js} +595 -482
- package/dist/chunk-ROMJP5D3.js.map +1 -0
- package/dist/docs-85fGFORb.d.ts +13 -0
- package/dist/docs.d.ts +1 -0
- package/dist/docs.js +7 -0
- package/dist/docs.js.map +1 -0
- package/dist/index.d.ts +50 -28
- package/dist/index.js +9 -7
- package/package.json +11 -8
- package/dist/chunk-2J5WHI6X.js +0 -2000
- package/dist/chunk-2J5WHI6X.js.map +0 -1
- package/dist/chunk-36C7A7DW.js +0 -2685
- package/dist/chunk-36C7A7DW.js.map +0 -1
- package/dist/chunk-3PLOAM2C.js +0 -2427
- package/dist/chunk-3PLOAM2C.js.map +0 -1
- package/dist/chunk-57GYK2EF.js +0 -2991
- package/dist/chunk-57GYK2EF.js.map +0 -1
- package/dist/chunk-ABLJBL5P.js +0 -2687
- package/dist/chunk-ABLJBL5P.js.map +0 -1
- package/dist/chunk-BOOUADRN.js +0 -1938
- package/dist/chunk-BOOUADRN.js.map +0 -1
- package/dist/chunk-BPWIETN5.js +0 -2983
- package/dist/chunk-BPWIETN5.js.map +0 -1
- package/dist/chunk-CMQB5G7R.js +0 -2968
- package/dist/chunk-CMQB5G7R.js.map +0 -1
- package/dist/chunk-I4B53KZ7.js +0 -2955
- package/dist/chunk-I4B53KZ7.js.map +0 -1
- package/dist/chunk-IPNUAUAN.js.map +0 -1
- package/dist/chunk-KZWL2S6E.js +0 -2869
- package/dist/chunk-KZWL2S6E.js.map +0 -1
- package/dist/chunk-LB2QBYW4.js +0 -2686
- package/dist/chunk-LB2QBYW4.js.map +0 -1
- package/dist/chunk-MR3AXSXL.js +0 -1977
- package/dist/chunk-MR3AXSXL.js.map +0 -1
- package/dist/chunk-NVSZXEZU.js +0 -2688
- package/dist/chunk-NVSZXEZU.js.map +0 -1
- package/dist/chunk-PD2WKAFE.js +0 -2531
- package/dist/chunk-PD2WKAFE.js.map +0 -1
- package/dist/chunk-PGY4ZDLD.js +0 -1843
- package/dist/chunk-PGY4ZDLD.js.map +0 -1
- package/dist/chunk-QDIAASH7.js +0 -2982
- package/dist/chunk-QDIAASH7.js.map +0 -1
- package/dist/chunk-UZWFLJGG.js +0 -2873
- package/dist/chunk-UZWFLJGG.js.map +0 -1
- package/dist/chunk-VFYTROYG.js +0 -2825
- package/dist/chunk-VFYTROYG.js.map +0 -1
- package/dist/chunk-ZTHKAJKT.js +0 -2981
- package/dist/chunk-ZTHKAJKT.js.map +0 -1
- package/dist/chunk-ZZULGRRE.js +0 -2991
- package/dist/chunk-ZZULGRRE.js.map +0 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@ snapshot, click a button by its ref, wait on a condition, and ask what changed.
|
|
|
7
7
|
It is deliberately thin. Every tool validates its arguments with zod, calls the
|
|
8
8
|
public `@termwright/driver` API, and renders the result. There is no locator
|
|
9
9
|
engine, no wait loop and no matching heuristic here — a behaviour that differed
|
|
10
|
-
between this server and the
|
|
10
|
+
between this server and the Native Host would be a bug in this package.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
|
@@ -47,28 +47,68 @@ process.on('SIGINT', () => void running.close());
|
|
|
47
47
|
// terminal.snapshot -> the compact format, plus refs / cursor / modes / scroll
|
|
48
48
|
Terminal t1 100x30 revision 42
|
|
49
49
|
semanticTree: available
|
|
50
|
-
dialog "Permission" ref=n7@42 bounds=(8,20,40,9) modal
|
|
51
|
-
button "Approve" ref=n8@42 bounds=(14,23,11,1) focused
|
|
50
|
+
dialog "Permission" ref=semantic:n7@42 bounds=(8,20,40,9) modal
|
|
51
|
+
button "Approve" ref=semantic:n8@42 bounds=(14,23,11,1) focused
|
|
52
|
+
button "Reject" ref=semantic:n9@42 bounds=(14,36,10,1)
|
|
52
53
|
visible text:
|
|
53
54
|
…
|
|
54
55
|
|
|
55
|
-
// terminal.click { "terminal": "t1", "ref": "
|
|
56
|
-
// terminal.wait_for { "terminal": "t1", "wait": "text", "text": "
|
|
56
|
+
// terminal.click { "terminal": "t1", "ref": "semantic:n9@42" }
|
|
57
|
+
// terminal.wait_for { "terminal": "t1", "wait": "text", "text": "Rejected" }
|
|
58
|
+
// terminal.wait_for { "terminal": "t1", "wait": "focused", "testId": "reject" }
|
|
57
59
|
// terminal.capture_since{ "terminal": "t1", "cursor": 42 } -> changed rows + subtrees
|
|
58
60
|
// terminal.close { "terminal": "t1" }
|
|
59
61
|
```
|
|
60
62
|
|
|
63
|
+
The text wait proves the PTY output. When the next operation requires a semantic
|
|
64
|
+
result, wait for that explicit state (`focused`, `checked`, `selected`, …)
|
|
65
|
+
before capturing. The condition must represent a change from the baseline; an
|
|
66
|
+
already-satisfied condition proves no new commit. Screen-only output and the
|
|
67
|
+
prefix of a future semantic frame are intentionally indistinguishable until the
|
|
68
|
+
probe publishes a causal signal.
|
|
69
|
+
|
|
61
70
|
Streamable HTTP, for hosts that connect over a socket:
|
|
62
71
|
|
|
63
72
|
```sh
|
|
64
|
-
termwright-mcp --http --port 7333
|
|
73
|
+
termwright-mcp --http --port 7333 --show-auth-token
|
|
74
|
+
# Explicit opt-in prints the endpoint and its fresh per-launch bearer token.
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Every MCP-bearing HTTP request, including initialize and `DELETE`, must send
|
|
78
|
+
`Authorization: Bearer <token>`; an allowlisted CORS preflight is the only
|
|
79
|
+
bearer-free request and cannot reach MCP routing. Library callers receive the
|
|
80
|
+
token as `handle.authToken`; the SDK transport accepts it through
|
|
81
|
+
`requestInit.headers`:
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
85
|
+
import { serveHttp } from '@termwright/mcp';
|
|
86
|
+
|
|
87
|
+
const handle = await serveHttp();
|
|
88
|
+
const transport = new StreamableHTTPClientTransport(
|
|
89
|
+
new URL(`http://127.0.0.1:${handle.port}/mcp`),
|
|
90
|
+
{ requestInit: { headers: { authorization: `Bearer ${handle.authToken}` } } },
|
|
91
|
+
);
|
|
65
92
|
```
|
|
66
93
|
|
|
67
94
|
Sessions are keyed by `Mcp-Session-Id` in this package's own `SessionRegistry`,
|
|
68
|
-
not inside transport objects
|
|
95
|
+
not inside transport objects. A session id is routing metadata, never a
|
|
96
|
+
credential: authentication happens before session lookup, refresh, initialize
|
|
97
|
+
or request-body buffering. Each session owns its terminals, `DELETE` disposes
|
|
69
98
|
them, and the ceiling (16 sessions, 16 terminals each) is enforced before a
|
|
70
99
|
transport exists.
|
|
71
100
|
|
|
101
|
+
The listener binds to loopback by default. A non-loopback `host` is refused
|
|
102
|
+
unless `allowNonLoopback: true` is set (CLI: `--allow-non-loopback`). That opt-in
|
|
103
|
+
does not add TLS: put a remotely reachable listener behind a private,
|
|
104
|
+
authenticated TLS boundary because the bearer otherwise crosses the network in
|
|
105
|
+
cleartext. Browser requests carrying `Origin` are rejected by default; an
|
|
106
|
+
embedding may allow exact HTTP(S) origins with `allowedOrigins`. A bounded
|
|
107
|
+
per-peer rate limiter protects authenticated work and accepted preflights
|
|
108
|
+
without letting invalid credentials or preflight traffic consume a legitimate
|
|
109
|
+
client's bucket; configure its window/request/client ceilings with `rateLimit`
|
|
110
|
+
when the deployment has a known proxy or concurrency envelope.
|
|
111
|
+
|
|
72
112
|
Streamable HTTP gives no disconnect signal, so a session also expires after
|
|
73
113
|
`idleTtlMs` without a request (10 minutes by default, `0` to disable). Every
|
|
74
114
|
request naming a session refreshes it; expiry runs the full teardown — terminals
|
|
@@ -77,19 +117,57 @@ stderr. stdio has no TTL: there, EOF on the pipe is the signal.
|
|
|
77
117
|
|
|
78
118
|
## Tools
|
|
79
119
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
`
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
`
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`
|
|
120
|
+
<!-- BEGIN GENERATED MCP TOOL SURFACE -->
|
|
121
|
+
<!-- Generated from packages/mcp/src/registry.ts; do not edit this block by hand. -->
|
|
122
|
+
### Live terminal tools
|
|
123
|
+
|
|
124
|
+
| Tool | Purpose |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `terminal.launch` | Starts a program in a real pseudo-terminal and returns a terminal handle plus the first snapshot. The child gets a minimal environment unless envMode is "inherit"; values passed in env are never echoed back. |
|
|
127
|
+
| `terminal.capabilities` | What this session supports: whether a semantic tree is published, which adapter publishes it, and the terminal geometry. Call it before relying on role-based targeting. |
|
|
128
|
+
| `terminal.snapshot` | One typed view of the terminal: compact semantic refs, visible text, cursor, terminal modes and scroll position. variant "full" writes the complete dump (text, ANSI, HTML, semantic tree) to disk and returns only refs plus the file path. The returned revision is the cursor for terminal.capture_since. |
|
|
129
|
+
| `terminal.capture_since` | Incremental view: the screen rows that differ and the semantic subtrees that were added, removed or updated in the latest committed semantic tree since the given cursor. A screen change alone does not imply a future semantic commit; wait for an explicit semantic state when the caller requires one. The cursor must be a revision this server handed out earlier (snapshot or capture_since); older cursors fail with history-truncated. |
|
|
130
|
+
| `terminal.query` | Resolves a target to refs without acting on it. Use it to check how many nodes a locator matches before clicking, or to turn a role/name into a ref. |
|
|
131
|
+
| `terminal.checkpoint` | Returns the atomic session/contract/screen/semantic identity used by revision-safe actions and waits. |
|
|
132
|
+
| `terminal.actionability` | Runs the same ActionPlanner used by execution, but sends no input. Reports every authoritative requirement and the chosen strategy or typed rejection. |
|
|
133
|
+
| `terminal.click` | Sends a real click mouse report through the pseudo-terminal. Fails closed with input-mode-disabled when the required tracking mode or encoding is disabled or unobservable. |
|
|
134
|
+
| `terminal.double_click` | Sends a real double-click mouse report through the pseudo-terminal. Fails closed with input-mode-disabled when the required tracking mode or encoding is disabled or unobservable. |
|
|
135
|
+
| `terminal.hover` | Sends a real motion mouse report through the pseudo-terminal. Fails closed with input-mode-disabled when the required tracking mode or encoding is disabled or unobservable. |
|
|
136
|
+
| `terminal.press` | Sends key chords as real bytes, honouring the modes the program enabled (application cursor keys, keypad). Examples: "Enter", "Escape", "Control+K Control+U". With a target, the node must already be focused. |
|
|
137
|
+
| `terminal.type` | Types text as individual keystrokes (not a paste). With a target, the node must already be focused; use terminal.fill for focus + replacement. |
|
|
138
|
+
| `terminal.fill` | Ensures the semantic control receives focus through the real input path, selects its current value, and types the replacement. |
|
|
139
|
+
| `terminal.check` | Uses the central action planner and real terminal input to check a checkbox or radio, then verifies semantic state. |
|
|
140
|
+
| `terminal.uncheck` | Uses the central action planner and real terminal input to uncheck a checkbox or radio, then verifies semantic state. |
|
|
141
|
+
| `terminal.paste` | Pastes text, wrapped in bracketed-paste markers when the program enabled that mode. Use it for multi-line input instead of terminal.type. |
|
|
142
|
+
| `terminal.write_raw` | Writes bytes to the pseudo-terminal verbatim — no newline, no key encoding. The escape hatch for sequences the key encoder does not model. |
|
|
143
|
+
| `terminal.drag` | Drags with real mouse reports: either from one target to another (toTarget), or between two cell positions inside the source target (from/to). |
|
|
144
|
+
| `terminal.wheel` | Sends wheel reports over a target. Positive deltaY scrolls down. |
|
|
145
|
+
| `terminal.resize` | Resizes the pseudo-terminal; the child sees a real SIGWINCH. |
|
|
146
|
+
| `terminal.signal` | Sends INT, TERM, KILL or HUP to the child. Destructive by design: terminal.close cleans up without signalling. |
|
|
147
|
+
| `terminal.scrollback` | Emulator-side history: read a line range, search it, or move the viewport. The child sees nothing — no input is sent. |
|
|
148
|
+
| `terminal.select_cells` | Selects a rectangle in the emulator (like a mouse selection). No input is sent. |
|
|
149
|
+
| `terminal.copy_selection` | Returns the text of the current selection and optionally clears it. |
|
|
150
|
+
| `terminal.wait_for` | Revision-driven waits — never a sleep. "text"/"title" wait for content, locator states use the driver's canonical Conditions, "quiet" explicitly waits for heuristic silence, "render" for a render after a given revision, "exit" for the child to exit. |
|
|
151
|
+
| `terminal.close` | Bounded physical cleanup: hangs up the pseudo-terminal and forgets the handle. Send signals explicitly with terminal.signal if the child must be killed first. |
|
|
152
|
+
|
|
153
|
+
### Trace tools
|
|
154
|
+
|
|
155
|
+
| Tool | Purpose |
|
|
156
|
+
| --- | --- |
|
|
157
|
+
| `trace.open` | Validates a .twtrace directory or zip and returns a handle plus its metadata: the recorded command, viewport, duration, exit status and whether the session published a semantic tree. Start every replay investigation here. |
|
|
158
|
+
| `trace.overview` | The shape of a recording: every step with its status and timing, the cast markers, the exit status, and which step failed. Use it to pick the moment worth reconstructing before calling trace.frame_at. |
|
|
159
|
+
| `trace.frame_at` | Rebuilds the screen at a moment — named by timeMs, stepIndex or marker — by replaying the recording into a headless emulator, and pairs it with the semantic tree of the nearest revision at or before that moment. Reads exactly like a live terminal.snapshot. |
|
|
160
|
+
| `trace.diff` | Reconstructs two moments of a recording and reports what moved: changed screen rows and changed semantic subtrees, in the same shape as terminal.capture_since on a live session. |
|
|
161
|
+
|
|
162
|
+
### Targeting
|
|
163
|
+
|
|
164
|
+
Targeting precedence is `ref`, `selector`, `testId`, `role` (+`name`), `label`, `text`, `screenText`.
|
|
165
|
+
|
|
166
|
+
`semanticTree: unavailable` means the program ships no integration — target physical output with `screenText`, never semantic `text` or `role`.
|
|
167
|
+
|
|
168
|
+
Names and text accept `/pattern/flags`. Locators are strict: more than one match returns
|
|
169
|
+
`ambiguous-locator` unless `nth` is explicit.
|
|
170
|
+
<!-- END GENERATED MCP TOOL SURFACE -->
|
|
93
171
|
|
|
94
172
|
## Replaying a recorded failure
|
|
95
173
|
|
|
@@ -102,8 +180,8 @@ same vocabulary as a live session.
|
|
|
102
180
|
// trace.frame_at { "traceId": "tr1", "stepIndex": 1 }
|
|
103
181
|
Terminal tr1 40x6 revision 2
|
|
104
182
|
semanticTree: available
|
|
105
|
-
dialog "Permission" ref=n1@2 modal
|
|
106
|
-
button "Approve" ref=n2@2 disabled
|
|
183
|
+
dialog "Permission" ref=semantic:n1@2 modal
|
|
184
|
+
button "Approve" ref=semantic:n2@2 disabled
|
|
107
185
|
visible text:
|
|
108
186
|
…
|
|
109
187
|
// trace.diff { "traceId": "tr1", "fromMs": 0, "toMs": 3000 } -> changed rows + subtrees
|
|
@@ -139,8 +217,8 @@ window; lower `screenshotScale` or resize the terminal.
|
|
|
139
217
|
|
|
140
218
|
## Refs and revisions
|
|
141
219
|
|
|
142
|
-
A ref is `n8@42`: node id at semantic revision 42 (
|
|
143
|
-
`
|
|
220
|
+
A ref is `semantic:n8@42`: node id at semantic revision 42 (screen matches get
|
|
221
|
+
`screen:1,2,9,1@7`). Refs go straight to `harness.locatorForRef()`, so they resolve
|
|
144
222
|
by node *identity* — two buttons with the same name stay distinct. A producer
|
|
145
223
|
which promises stable identity can resolve that node again in later revisions.
|
|
146
224
|
Frame-local identities and grid refs remain revision-bound; take a fresh
|
|
@@ -148,11 +226,14 @@ snapshot when either becomes stale.
|
|
|
148
226
|
|
|
149
227
|
`terminal.snapshot` also returns a screen `revision`; pass it back as the
|
|
150
228
|
`cursor` of `terminal.capture_since` to get only the rows and semantic subtrees
|
|
151
|
-
that changed
|
|
152
|
-
|
|
229
|
+
that changed in the latest committed tree. A changed screen does not promise a
|
|
230
|
+
future semantic commit; wait for an explicit semantic condition when one is
|
|
231
|
+
required. Cursors the server never handed out fail with `history-truncated` (the
|
|
232
|
+
last 16 captures per terminal are retained).
|
|
153
233
|
|
|
154
234
|
Programs without a framework probe or custom semantic producer report
|
|
155
|
-
`semanticTree: unavailable`. There are no invented roles: target
|
|
235
|
+
`semanticTree: unavailable`. There are no invented roles: target physical
|
|
236
|
+
output with `screenText`.
|
|
156
237
|
|
|
157
238
|
## Application logs
|
|
158
239
|
|
|
@@ -216,7 +297,7 @@ are the one thing never recorded: the driver keeps their size only.
|
|
|
216
297
|
Failures come back as tool results with `isError` set. The text content reads
|
|
217
298
|
|
|
218
299
|
```
|
|
219
|
-
error stale-snapshot: ref n8@42 no longer exists at semantic revision 43
|
|
300
|
+
error stale-snapshot: ref semantic:n8@42 no longer exists at semantic revision 43
|
|
220
301
|
suggestion: re-resolve the locator; the node identity is no longer present
|
|
221
302
|
semanticTree: true
|
|
222
303
|
```
|
package/dist/bin.js
CHANGED
package/dist/bin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/** The `termwright-mcp` executable. All logic lives in `cli.ts`. */\nimport { main } from './cli.js';\n\n// `termwright-mcp agent-context | head` closes the pipe early; that is a normal\n// end of output for a CLI, not a crash.\nprocess.stdout.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') process.exit(0);\n throw error;\n});\n\nawait main();\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/bin.ts"],"sourcesContent":["#!/usr/bin/env node\n/** The `termwright-mcp` executable. All logic lives in `cli.ts`. */\nimport { main } from './cli.js';\n\n// `termwright-mcp agent-context | head` closes the pipe early; that is a normal\n// end of output for a CLI, not a crash.\nprocess.stdout.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') process.exit(0);\n throw error;\n});\n\nawait main();\n"],"mappings":";;;;;;;AAMA,QAAQ,OAAO,GAAG,SAAS,CAAC,UAAiC;AAC3D,MAAI,MAAM,SAAS,QAAS,SAAQ,KAAK,CAAC;AAC1C,QAAM;AACR,CAAC;AAED,MAAM,KAAK;","names":[]}
|