pw-repl 0.11.0 → 0.11.2
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/AGENTS.md +1 -1
- package/README.md +15 -6
- package/bin/pw-repl.js +4 -3
- package/lib/help.js +5 -5
- package/lib/runner.js +1 -1
- package/lib/syntax.js +1 -1
- package/lib/tabs.js +2 -2
- package/lib/watch.js +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +12 -9
package/AGENTS.md
CHANGED
|
@@ -25,7 +25,7 @@ If you hit a limitation or write a workaround, consider adding the capability to
|
|
|
25
25
|
runs commands one at a time (`quit` and `dialog` skip its queue); `lib/server.js` is the command
|
|
26
26
|
server; `lib/send.js` and `lib/client.js` are `send` and `where`; `lib/background.js` is
|
|
27
27
|
`serve --background`, `attach` and `stop`; `lib/launch.js` is `--launch`; `lib/syntax.js` is how a
|
|
28
|
-
command line's words are read, shared with `send
|
|
28
|
+
command line's words are read, shared with `send`, and its `{{ PW_NAME }}` variables; `lib/cli-names.js` maps playwright-cli's command
|
|
29
29
|
names to these; `lib/state.js` holds the session state, with each client's selected tab;
|
|
30
30
|
`lib/output.js` routes all output so the server can return it; `lib/skill.js` stamps the skill
|
|
31
31
|
`pw-repl skill` prints, and gives `where` its hash.
|
package/README.md
CHANGED
|
@@ -88,7 +88,7 @@ the Docker bridge gateway, `ip route | awk '/default/ {print $3}'`).
|
|
|
88
88
|
|
|
89
89
|
| I want to… | Commands |
|
|
90
90
|
| ------------------------------------ | ------------------------------------------------------------------------------------- |
|
|
91
|
-
| see where I am | `tab` (it marks the tab you are looking at `(
|
|
91
|
+
| see where I am | `tab` (it marks the tab you are looking at `(visible)`), `info` |
|
|
92
92
|
| see what is on the page | `snapshot`, `screenshot` |
|
|
93
93
|
| do something on it | `click`, `fill`, `press` |
|
|
94
94
|
| wait for a page or element | `wait load`, `wait <selector>`, `wait <selector> --gone` |
|
|
@@ -179,6 +179,14 @@ Several agents can share one REPL: `pw-repl send -c <name>` (or `PW_CLIENT=<name
|
|
|
179
179
|
selected tab of its own, shown in the pane as `[server:<name>]`. `modes` says which client turned each mode on,
|
|
180
180
|
and `modes off --mine` turns off only the sender's own.
|
|
181
181
|
|
|
182
|
+
A password can be typed as a variable: `PW_PASSWORD=... pw-repl send fill '#password' "{{ PW_PASSWORD }}"`,
|
|
183
|
+
or the same line in a file run with `send --file`, as `watch save` writes it. `send` takes the value of
|
|
184
|
+
`{{ PW_NAME }}` from its own environment and passes it along with the command; the REPL puts it in only as
|
|
185
|
+
it runs the command, so the file, and the command as the pane, the log and the prompt's history show it,
|
|
186
|
+
never hold it. Output that reads it back can: `snapshot`, `html` and `attrs` hide a password field's value,
|
|
187
|
+
but `eval` reading the field, a field a Show password button made plain text (`help snapshot`), or page
|
|
188
|
+
text that repeats it do not. Only `PW_` names are variables (`pw-repl --help` has the rest).
|
|
189
|
+
|
|
182
190
|
### The HTTP protocol
|
|
183
191
|
|
|
184
192
|
With `pw-repl serve`, the server speaks HTTP with JSON, for clients of your own:
|
|
@@ -188,11 +196,12 @@ curl --unix-socket /tmp/playwright-repl.sock -H 'Content-Type: application/json'
|
|
|
188
196
|
-d '{"command": "info"}' http://localhost/run
|
|
189
197
|
```
|
|
190
198
|
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
199
|
+
The body may also have `"client": "<name>"`, and `"vars": {"PW_NAME": "<value>"}` with the values of the
|
|
200
|
+
`{{ PW_NAME }}` variables in the command, one line each (else 400). It returns `{"status": "ok" | "error", "output": "..."}`, plus
|
|
201
|
+
`"unconfirmed": true` when the command may or may not have done what it was sent to do (it timed out, or
|
|
202
|
+
the REPL quit while it ran and it was not read-only). While the REPL starts, `/run` answers 503 with
|
|
203
|
+
`"starting": true`: retry. `GET /health` says `"status": "starting"` until it serves, then `"ok"`.
|
|
204
|
+
`pw-repl serve <port>` serves TCP on 127.0.0.1 instead; other addresses are refused.
|
|
196
205
|
|
|
197
206
|
## Tests
|
|
198
207
|
|
package/bin/pw-repl.js
CHANGED
|
@@ -45,13 +45,14 @@ folder, as on send's command line, and # comments and blank lines skipped. It st
|
|
|
45
45
|
command that fails, saying its line, with its exit status. -t is each command's own limit. Other
|
|
46
46
|
clients' commands can run between its lines, on their own tabs.
|
|
47
47
|
|
|
48
|
-
A {{ PW_NAME }} in a command, or in a line of the file, is
|
|
49
|
-
|
|
48
|
+
A {{ PW_NAME }} in a command, or in a line of the file, is a variable: send takes its value from its own
|
|
49
|
+
environment (PW_NAME) and passes it along with the command, not in it, e.g. a password kept out of the
|
|
50
|
+
file and out of the command as the REPL logs it. In a file: fill '#password' "{{ PW_PASSWORD }}",
|
|
50
51
|
run with PW_PASSWORD=... pw-repl send --file login.txt; or one command: PW_PASSWORD=... pw-repl send
|
|
51
52
|
fill '#password' "{{ PW_PASSWORD }}". Only names starting PW_ are variables: any other {{ ... }}, as in a
|
|
52
53
|
page's template text, is sent as written. In a file, put it in double quotes: there its value is
|
|
53
54
|
escaped as they read it (in single quotes, which escape nothing, a value with a ' is refused). The REPL
|
|
54
|
-
|
|
55
|
+
puts it in only as it runs the command, and its pane and log show the command as written. A variable that
|
|
55
56
|
is not set stops the command, or the file at that line, before it is sent. --no-vars sends
|
|
56
57
|
{{ PW_NAME }} as it is. Variables need the command server (pw-repl serve), not the tmux pane.
|
|
57
58
|
|
package/lib/help.js
CHANGED
|
@@ -152,7 +152,7 @@ const COMMANDS = {
|
|
|
152
152
|
usage: 'tab [<index>|<url-part>|new [url]|close [url-part]]',
|
|
153
153
|
summary: 'list tabs (* is selected); select, open or close one',
|
|
154
154
|
args: [
|
|
155
|
-
['(none)', 'lists the tabs: * marks the selected one, (
|
|
155
|
+
['(none)', 'lists the tabs: * marks the selected one, (visible) the one the browser shows, and each says who opened it with tab new.'],
|
|
156
156
|
['<index>', 'selects by the number in your latest listing (each client has its own); numbers change when tabs open or close.'],
|
|
157
157
|
['<url-part>', 'selects the one tab whose URL contains it; refused if none or several do.'],
|
|
158
158
|
['new [url]', 'opens a tab behind the one in front, so it does not take the window from whoever is using it, and selects it. The URL is read as goto reads it.'],
|
|
@@ -161,11 +161,11 @@ const COMMANDS = {
|
|
|
161
161
|
detail: `Closing the selected tab goes back to the previous tab if tab new opened it; otherwise no tab is
|
|
162
162
|
selected, and commands that need one refuse until one is.
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
Visible: (visible) marks the tab the browser shows in its window, the one the user sees; (visible,
|
|
165
165
|
focused) when its window has the keyboard too, and it is not in the address bar. A tab a client has
|
|
166
166
|
selected or opened is never marked: pw-repl tells it that it is in front, so that it keeps drawing and
|
|
167
167
|
clicks in it work while it is behind another, and so cannot ask it. The user's other tabs are told
|
|
168
|
-
nothing, and say truly whether they are seen. Each window
|
|
168
|
+
nothing, and say truly whether they are seen. Each window shows a tab, so with several windows
|
|
169
169
|
(an incognito one, say) several tabs are marked; focused is the one with the keyboard.`,
|
|
170
170
|
},
|
|
171
171
|
goto: {
|
|
@@ -353,8 +353,8 @@ describing it: click, fill, select, check, press, and wait load after a step tha
|
|
|
353
353
|
element is found by its id, data-testid or name, its text, or else its place in the page, which a change
|
|
354
354
|
to the page can break. A select step chooses the option by its value. What was typed into a password
|
|
355
355
|
field (or any field, with --no-values) is a variable named after the field, e.g. fill '#password'
|
|
356
|
-
"{{ PW_PASSWORD }}"
|
|
357
|
-
|
|
356
|
+
"{{ PW_PASSWORD }}": run it with PW_PASSWORD=... pw-repl send --file login.txt, and send passes the
|
|
357
|
+
value along for the REPL to put in, which shows the line as written. A step that cannot be a command (a value with a line break, an element in a frame, or
|
|
358
358
|
one no selector finds alone) stays a comment saying why, and the save says how many there are.
|
|
359
359
|
|
|
360
360
|
Shared: every client sees the same steps, and watch new's place is shared.`,
|
package/lib/runner.js
CHANGED
|
@@ -188,7 +188,7 @@ function enqueue(line) {
|
|
|
188
188
|
// someone watching sees everything an agent does.
|
|
189
189
|
// client names the sender: its commands show as [server:<client>] and use its own selected tab.
|
|
190
190
|
// vars: values for the {{ PW_NAME }} variables in text (from send), filled in to run it; text is shown
|
|
191
|
-
// as written, so a password in one
|
|
191
|
+
// as written, so a password in one is not in the command as the pane, the log and the prompt's history show it.
|
|
192
192
|
function submit(text, client = null, vars = null) {
|
|
193
193
|
let run = text;
|
|
194
194
|
if (vars) {
|
package/lib/syntax.js
CHANGED
|
@@ -70,7 +70,7 @@ function variablesIn(line) {
|
|
|
70
70
|
// values: { NAME: value }. Throws on a variable it has no value for.
|
|
71
71
|
function expandVariables(line, values) {
|
|
72
72
|
const value = name => {
|
|
73
|
-
if (typeof values[name] !== 'string') throw new Error(`{{ ${name} }} has no value: send
|
|
73
|
+
if (typeof values[name] !== 'string') throw new Error(`{{ ${name} }} has no value: send passes it along from ${name} in its environment`);
|
|
74
74
|
return values[name];
|
|
75
75
|
};
|
|
76
76
|
return line.replace(new RegExp(`"(?:[^"\\\\]|\\\\.)*"|'[^']*'|${VARIABLE.source}`, 'g'), (whole, name) => {
|
package/lib/tabs.js
CHANGED
|
@@ -105,7 +105,7 @@ async function pageForTarget(ctx, targetId) {
|
|
|
105
105
|
}
|
|
106
106
|
|
|
107
107
|
// Every tab, URL first, with * on the selected one. The numbers are what tab <index> uses.
|
|
108
|
-
// Whether a tab is the one in front in its window, as the tab itself says: '
|
|
108
|
+
// Whether a tab is the one in front in its window, as the tab itself says: 'visible', 'visible,
|
|
109
109
|
// focused' (its window has the keyboard, and not in the address bar), or null. Not for a tab a client
|
|
110
110
|
// uses, which is told it is in front so that it draws (lib/cdp.js); nor for one that does not answer
|
|
111
111
|
// at once (a dialog open, a frozen tab). Each window has one in front, an incognito one too.
|
|
@@ -114,7 +114,7 @@ async function inFront(p) {
|
|
|
114
114
|
const read = p.evaluate(() => [document.visibilityState, document.hasFocus()]).catch(() => null);
|
|
115
115
|
const seen = await Promise.race([read, new Promise(resolve => setTimeout(resolve, FRONT_READ_TIMEOUT, null))]);
|
|
116
116
|
if (!seen || seen[0] !== 'visible') return null;
|
|
117
|
-
return seen[1] ? '
|
|
117
|
+
return seen[1] ? 'visible, focused' : 'visible';
|
|
118
118
|
}
|
|
119
119
|
|
|
120
120
|
async function listTabs() {
|
package/lib/watch.js
CHANGED
|
@@ -553,7 +553,7 @@ function showWatch(watch, all) {
|
|
|
553
553
|
|
|
554
554
|
// watch save: the steps as commands for send --file to run again, a step that cannot be one (a value
|
|
555
555
|
// with a line break, an element in a frame) as a comment saying so. What was typed into a password field,
|
|
556
|
-
// or with --no-values, is a {{ PW_NAME }} variable, named after the field,
|
|
556
|
+
// or with --no-values, is a {{ PW_NAME }} variable, named after the field, set when send --file runs it. A
|
|
557
557
|
// selector is quoted as lib/syntax.js reads one: single quotes, which escape nothing, unless it holds
|
|
558
558
|
// one. A value is always double-quoted, so an empty one, or one that starts with -, stays the value.
|
|
559
559
|
function savedCommands(watch, p) {
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -129,12 +129,15 @@ REPL), or why neither is reachable, without running anything.
|
|
|
129
129
|
`send --file take.txt` runs a file of commands, one per line, as typed at the prompt (no shell quoting;
|
|
130
130
|
files relative to your folder, as on `send`'s command line; `#` comments), and stops at the first that
|
|
131
131
|
fails, saying which line. It is the way to record a video without gaps (`help record`), and to keep a
|
|
132
|
-
repro to run again. A `{{ PW_NAME }}` in a line is
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
132
|
+
repro to run again. A `{{ PW_NAME }}` in a line is a variable, best in double quotes (`fill '#password'
|
|
133
|
+
"{{ PW_PASSWORD }}"`): `send` takes its value from its own environment and passes it along with the
|
|
134
|
+
line, not in it; the REPL puts it in only as it runs the line, and its pane and log show the line as
|
|
135
|
+
written, so neither the file nor the line as logged holds the secret. Only `PW_` names are variables;
|
|
136
|
+
any other `{{ ... }}` (a page's template text) is sent as written. One that is not set stops the file;
|
|
137
|
+
`--no-vars` sends it as written. A single `send` takes them too: `PW_PASSWORD=... pw-repl send fill
|
|
138
|
+
'#password' "{{ PW_PASSWORD }}"` types a password without it in the command. What reads it back can
|
|
139
|
+
still show it: `snapshot`, `html` and `attrs` hide a password field's value, `eval` on the field does
|
|
140
|
+
not.
|
|
138
141
|
|
|
139
142
|
`send` takes one command; chain several in your shell. Use `&&` when a step only makes sense if the one
|
|
140
143
|
before it worked: it stops at any exit status but 0, a failure and an outcome not confirmed included.
|
|
@@ -163,8 +166,8 @@ shown yet. For a tab they have not opened yet, `watch on --next-tab [url-part]`
|
|
|
163
166
|
watches it from its first page. `watch save <file>` writes the steps as commands that `send --file`
|
|
164
167
|
runs again, a repro to keep. What is typed is recorded, except in password fields; `watch on
|
|
165
168
|
--no-values` leaves it out. The file has a `{{ PW_PASSWORD }}` variable where a password was typed,
|
|
166
|
-
|
|
167
|
-
|
|
169
|
+
set when it runs (`PW_PASSWORD=... pw-repl send --file login.txt`), so the file can be shared without
|
|
170
|
+
it.
|
|
168
171
|
|
|
169
172
|
When watching someone:
|
|
170
173
|
|
|
@@ -214,7 +217,7 @@ pw-repl send -c <name> close # your modes off; tab close
|
|
|
214
217
|
|
|
215
218
|
- The browser may have tabs that are not yours, and someone may be using it. Whether to read or act in
|
|
216
219
|
one of those tabs, or to open your own (`tab new <url>`), depends on the task; when that is not clear,
|
|
217
|
-
ask. `tab` marks `(
|
|
220
|
+
ask. `tab` marks `(visible)` the tab the user sees: "this page" is usually that one. A tab you have
|
|
218
221
|
selected is never marked (help tab says why).
|
|
219
222
|
- No tab is selected when the REPL starts. A start URL's tab, marked `(the start URL)` in `tab`, is
|
|
220
223
|
selected at its prompt and for `send` without a client name; a named client (`-c`) selects it with
|