playwright-repl 0.24.0 → 0.25.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 CHANGED
@@ -25,6 +25,33 @@ npm install -g playwright-repl
25
25
  npx playwright install chromium
26
26
  ```
27
27
 
28
+ ## AI Skills
29
+
30
+ playwright-repl includes an AI skill file that teaches agents (Claude Code, Cursor, etc.) how to use `pw-cli` via Bash — no MCP needed.
31
+
32
+ ### Claude Code
33
+
34
+ ```bash
35
+ # Project-level (this project only)
36
+ mkdir -p .claude/skills/playwright-repl
37
+ cp node_modules/playwright-repl/skills/SKILL.md .claude/skills/playwright-repl/SKILL.md
38
+
39
+ # Or global (all projects)
40
+ mkdir -p ~/.claude/skills/playwright-repl
41
+ cp node_modules/playwright-repl/skills/SKILL.md ~/.claude/skills/playwright-repl/SKILL.md
42
+ ```
43
+
44
+ Then invoke with `/playwright-repl` or let Claude auto-detect it from the skill description.
45
+
46
+ ### Cursor
47
+
48
+ ```bash
49
+ mkdir -p .cursor/skills/playwright-repl
50
+ cp node_modules/playwright-repl/skills/SKILL.md .cursor/skills/playwright-repl/SKILL.md
51
+ ```
52
+
53
+ The skill defines `allowed-tools: Bash(pw-cli:*)` so the agent can run browser commands directly.
54
+
28
55
  ## Connection Modes
29
56
 
30
57
  | Mode | Flag | How it works |
@@ -50,12 +77,59 @@ playwright-repl --bridge # start bridge server, wait for ex
50
77
  playwright-repl --bridge --replay script.pw # replay a script via bridge
51
78
  playwright-repl --bridge --replay examples/ # replay all .pw files
52
79
  playwright-repl --bridge --bridge-port 9877 # custom port (default 9876)
80
+ playwright-repl --bridge --command "snapshot" # run one command and exit
53
81
  ```
54
82
 
55
83
  The extension connects automatically — no need to open the side panel.
56
84
 
57
85
  > Requires the [Dramaturg Chrome extension](../extension/README.md).
58
86
 
87
+ ### HTTP Mode
88
+
89
+ Add `--http` to any mode to start an HTTP server alongside the REPL. This lets other processes send commands without starting a new browser session — ideal for AI tools, scripts, and CI.
90
+
91
+ ```bash
92
+ # Terminal 1: Start REPL with HTTP server
93
+ playwright-repl --http # standalone + HTTP (port 9223)
94
+ playwright-repl --bridge --http # bridge + HTTP
95
+ playwright-repl --http --http-port 9224 # custom port
96
+
97
+ # Terminal 2: Send commands via HTTP (fast — reuses existing session)
98
+ playwright-repl --http --command "snapshot"
99
+ playwright-repl --http --command "click e5"
100
+ ```
101
+
102
+ The HTTP server exposes:
103
+ - `GET /health` — check if server is alive
104
+ - `POST /run` — run a command: `{"command": "snapshot"}`
105
+ - `POST /run-script` — run a multi-line script: `{"script": "...", "language": "pw"}`
106
+
107
+ Works with `curl` too:
108
+ ```bash
109
+ curl -X POST http://localhost:9223/run -d '{"command":"snapshot"}'
110
+ ```
111
+
112
+ ## pw-cli — Quick Command Shorthand
113
+
114
+ `pw-cli` is a shorthand for `playwright-repl --http --command`. When given arguments, it sends the command via HTTP to a running session (REPL, MCP, or VS Code):
115
+
116
+ ```bash
117
+ pw-cli "snapshot" # → playwright-repl --http --command "snapshot"
118
+ pw-cli "click e5" # → playwright-repl --http --command "click e5"
119
+ pw-cli "await page.title()" # JavaScript works too
120
+ pw-cli # no args → starts interactive REPL
121
+ ```
122
+
123
+ Requires a running `--http` server (or MCP server) on port 9223. Start one first:
124
+
125
+ ```bash
126
+ # Option 1: REPL with HTTP
127
+ playwright-repl --http
128
+
129
+ # Option 2: MCP server (HTTP starts automatically)
130
+ # (launched by Claude Desktop / Claude Code)
131
+ ```
132
+
59
133
  ## Usage
60
134
 
61
135
  ```bash
@@ -77,6 +151,9 @@ playwright-repl --replay session.pw --step
77
151
  # Start REPL with recording enabled
78
152
  playwright-repl --record my-test.pw
79
153
 
154
+ # Run a single command and exit
155
+ playwright-repl --bridge --command "snapshot"
156
+
80
157
  # Pipe commands
81
158
  echo -e "goto https://example.com\nsnapshot" | playwright-repl
82
159
  ```
@@ -97,6 +174,9 @@ Both modes auto-detect keyword commands and JavaScript expressions. For DOM acce
97
174
  | `--headless` | Run browser in headless mode (default: headed) |
98
175
  | `--bridge` | Connect to existing Chrome via WebSocket bridge |
99
176
  | `--bridge-port <port>` | Bridge server port (default: `9876`) |
177
+ | `--http` | Start HTTP server for external command access (port `9223`) |
178
+ | `--http-port <port>` | HTTP server port (default: `9223`) |
179
+ | `--command <cmd>` | Run a single command, print output, and exit |
100
180
  | `--config <file>` | Path to config file |
101
181
  | `--replay <files...>` | Replay `.pw` or `.js` file(s) or folder(s) |
102
182
  | `--record <file>` | Start REPL with recording to file |
package/dist/engine.js CHANGED
@@ -393,4 +393,3 @@ function formatResult(result) {
393
393
  }
394
394
  return { isError, text, image };
395
395
  }
396
- //# sourceMappingURL=engine.js.map
package/dist/index.js CHANGED
@@ -14,4 +14,3 @@ export { Engine } from './engine.js';
14
14
  // CLI-specific
15
15
  export { SessionRecorder, SessionPlayer } from './recorder.js';
16
16
  export { startRepl } from './repl.js';
17
- //# sourceMappingURL=index.js.map
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Usage:
6
6
  * playwright-repl [options]
7
+ * playwright-repl --bridge --command "snapshot"
7
8
  * playwright-repl --replay session.pw
8
9
  * playwright-repl --replay session.pw --step
9
10
  * playwright-repl --replay file1.pw file2.pw
@@ -1 +1 @@
1
- {"version":3,"file":"playwright-repl.d.ts","sourceRoot":"","sources":["../src/playwright-repl.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;GAUG"}
1
+ {"version":3,"file":"playwright-repl.d.ts","sourceRoot":"","sources":["../src/playwright-repl.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;GAWG"}
@@ -4,6 +4,7 @@
4
4
  *
5
5
  * Usage:
6
6
  * playwright-repl [options]
7
+ * playwright-repl --bridge --command "snapshot"
7
8
  * playwright-repl --replay session.pw
8
9
  * playwright-repl --replay session.pw --step
9
10
  * playwright-repl --replay file1.pw file2.pw
@@ -13,8 +14,8 @@
13
14
  import { minimist } from '@playwright-repl/core';
14
15
  import { startRepl } from './repl.js';
15
16
  const args = minimist(process.argv.slice(2), {
16
- boolean: ['headed', 'headless', 'persistent', 'help', 'step', 'silent', 'spawn', 'bridge', 'engine', 'include-snapshot', 'verbose'],
17
- string: ['session', 'browser', 'profile', 'config', 'replay', 'record', 'connect', 'port', 'cdp-port', 'bridge-port'],
17
+ boolean: ['headed', 'headless', 'persistent', 'help', 'step', 'silent', 'spawn', 'bridge', 'engine', 'include-snapshot', 'verbose', 'http', 'interactive'],
18
+ string: ['session', 'browser', 'profile', 'config', 'replay', 'record', 'connect', 'port', 'cdp-port', 'bridge-port', 'command', 'http-port'],
18
19
  alias: { s: 'session', h: 'help', b: 'browser', q: 'silent' },
19
20
  default: { session: 'default' },
20
21
  });
@@ -43,6 +44,12 @@ Options:
43
44
  --cdp-port <number> Chrome CDP port (default: 9222)
44
45
  --include-snapshot Include snapshot in update command responses
45
46
  --verbose Show raw response headers (### Result, ### Snapshot, etc.)
47
+ --http Start HTTP server for external command access (port 9223).
48
+ Runs as a console (no interactive prompt) — pair with --interactive
49
+ to also get a readline REPL.
50
+ --http-port <port> HTTP server port (default: 9223)
51
+ --interactive Force interactive readline prompt even when --http is set
52
+ --command <cmd> Run a single command, print output, and exit
46
53
  --config <file> Path to config file
47
54
  --replay <files...> Replay .pw file(s) or folder(s)
48
55
  --record <file> Start REPL with recording to file
@@ -73,6 +80,7 @@ Examples:
73
80
  playwright-repl --replay login.pw --step # step through replay
74
81
  playwright-repl --replay tests/ # replay all .pw files in folder
75
82
  playwright-repl --replay a.pw b.pw # replay multiple files
83
+ playwright-repl --bridge --command "snapshot" # run one command and exit
76
84
  echo "open https://example.com" | playwright-repl # pipe commands
77
85
  `);
78
86
  process.exit(0);
@@ -84,6 +92,7 @@ if (args.replay) {
84
92
  for (const a of args._)
85
93
  replayFiles.push(String(a));
86
94
  }
95
+ const commandArg = args.command;
87
96
  startRepl({
88
97
  session: args.session,
89
98
  headed: args.headless ? false : args.headed ? true : undefined,
@@ -96,11 +105,15 @@ startRepl({
96
105
  cdpPort: args['cdp-port'] ? parseInt(args['cdp-port'], 10) : undefined,
97
106
  config: args.config,
98
107
  replay: replayFiles.length > 0 ? replayFiles : undefined,
108
+ command: commandArg,
109
+ silent: args.silent || !!commandArg,
99
110
  record: args.record,
100
111
  step: args.step,
101
- silent: args.silent,
102
112
  bridge: args.bridge,
103
113
  engine: args.engine,
114
+ http: args.http,
115
+ httpPort: args['http-port'] ? parseInt(args['http-port'], 10) : undefined,
116
+ interactive: args.interactive,
104
117
  bridgePort: args['bridge-port'] ? parseInt(args['bridge-port'], 10) : undefined,
105
118
  includeSnapshot: args['include-snapshot'],
106
119
  verbose: args.verbose,
@@ -108,4 +121,3 @@ startRepl({
108
121
  console.error(`Fatal: ${err.message}`);
109
122
  process.exit(1);
110
123
  });
111
- //# sourceMappingURL=playwright-repl.js.map
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pw-cli — shorthand for playwright-repl --http --command.
4
+ *
5
+ * Usage:
6
+ * pw-cli "snapshot" # → --http --command "snapshot"
7
+ * pw-cli "click e5" # → --http --command "click e5"
8
+ * pw-cli # → starts interactive REPL (same as playwright-repl)
9
+ * pw-cli --bridge # → passes flags through to playwright-repl
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=pw-cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pw-cli.d.ts","sourceRoot":"","sources":["../src/pw-cli.ts"],"names":[],"mappings":";AAEA;;;;;;;;GAQG"}
package/dist/pw-cli.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pw-cli — shorthand for playwright-repl --http --command.
4
+ *
5
+ * Usage:
6
+ * pw-cli "snapshot" # → --http --command "snapshot"
7
+ * pw-cli "click e5" # → --http --command "click e5"
8
+ * pw-cli # → starts interactive REPL (same as playwright-repl)
9
+ * pw-cli --bridge # → passes flags through to playwright-repl
10
+ */
11
+ import { startRepl } from './repl.js';
12
+ import { minimist } from '@playwright-repl/core';
13
+ const args = minimist(process.argv.slice(2), {
14
+ boolean: ['headed', 'headless', 'bridge', 'http', 'interactive', 'help'],
15
+ string: ['http-port', 'bridge-port', 'command'],
16
+ alias: { h: 'help' },
17
+ });
18
+ if (args.help) {
19
+ console.log(`
20
+ pw-cli — shorthand for playwright-repl
21
+
22
+ Usage:
23
+ pw-cli "snapshot" # send command via HTTP (fast)
24
+ pw-cli "click e5" # send command via HTTP
25
+ pw-cli # start interactive REPL
26
+ pw-cli --bridge # start bridge mode REPL
27
+ pw-cli --http # start REPL with HTTP server
28
+
29
+ Examples:
30
+ pw-cli "goto https://example.com"
31
+ pw-cli "await page.title()"
32
+ pw-cli "screenshot"
33
+ `);
34
+ process.exit(0);
35
+ }
36
+ // If positional args given, treat as --http --command
37
+ const positional = args._;
38
+ if (positional.length > 0) {
39
+ const command = positional.join(' ');
40
+ startRepl({
41
+ command,
42
+ http: true,
43
+ httpPort: args['http-port'] ? parseInt(args['http-port'], 10) : undefined,
44
+ silent: true,
45
+ }).catch((err) => {
46
+ console.error(err.message);
47
+ process.exit(1);
48
+ });
49
+ }
50
+ else {
51
+ // No args — start interactive REPL, pass through flags
52
+ startRepl({
53
+ bridge: args.bridge,
54
+ http: args.http,
55
+ httpPort: args['http-port'] ? parseInt(args['http-port'], 10) : undefined,
56
+ bridgePort: args['bridge-port'] ? parseInt(args['bridge-port'], 10) : undefined,
57
+ interactive: args.interactive,
58
+ headed: args.headless ? false : args.headed ? true : undefined,
59
+ }).catch((err) => {
60
+ console.error(`Fatal: ${err.message}`);
61
+ process.exit(1);
62
+ });
63
+ }
package/dist/recorder.js CHANGED
@@ -204,4 +204,3 @@ export class SessionManager {
204
204
  get player() { return this.#player; }
205
205
  get step() { return this.#step; }
206
206
  }
207
- //# sourceMappingURL=recorder.js.map
package/dist/repl.d.ts CHANGED
@@ -13,9 +13,13 @@ export interface ReplOpts extends EngineOpts {
13
13
  record?: string;
14
14
  step?: boolean;
15
15
  silent?: boolean;
16
+ command?: string;
16
17
  bridge?: boolean;
17
18
  bridgePort?: number;
18
19
  engine?: boolean;
20
+ http?: boolean;
21
+ httpPort?: number;
22
+ interactive?: boolean;
19
23
  includeSnapshot?: boolean;
20
24
  verbose?: boolean;
21
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"repl.d.ts","sourceRoot":"","sources":["../src/repl.ts"],"names":[],"mappings":"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"}
package/dist/repl.js CHANGED
@@ -7,6 +7,7 @@ import readline from 'node:readline';
7
7
  import path from 'node:path';
8
8
  import fs from 'node:fs';
9
9
  import os from 'node:os';
10
+ import http from 'node:http';
10
11
  import { replVersion, parseInput, ALIASES, ALL_COMMANDS, buildCompletionItems, c, prettyJson, BridgeServer, COMMANDS, CATEGORIES, JS_CATEGORIES, filterResponse as filterResponseBase, resolveArgs, handleLocalCommand, } from '@playwright-repl/core';
11
12
  import { Engine } from './engine.js';
12
13
  import { SessionManager } from './recorder.js';
@@ -858,11 +859,146 @@ async function startBridgeLoop(opts, srv) {
858
859
  rl.close();
859
860
  });
860
861
  }
862
+ // ─── HTTP server for --http mode ─────────────────────────────────────────────
863
+ const DEFAULT_HTTP_PORT = 9223;
864
+ function httpTs() {
865
+ const d = new Date();
866
+ const pad = (n) => n.toString().padStart(2, '0');
867
+ return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
868
+ }
869
+ function startHttpServer(port, runner, log) {
870
+ return new Promise((resolve, reject) => {
871
+ const server = http.createServer(async (req, res) => {
872
+ res.setHeader('Access-Control-Allow-Origin', '*');
873
+ if (req.method === 'GET' && req.url === '/health') {
874
+ res.writeHead(200, { 'Content-Type': 'application/json' });
875
+ res.end(JSON.stringify({ status: 'ok' }));
876
+ return;
877
+ }
878
+ if (req.method === 'POST' && req.url === '/run') {
879
+ const t0 = Date.now();
880
+ let command = '';
881
+ try {
882
+ const body = await readBody(req);
883
+ ({ command } = JSON.parse(body));
884
+ log(`${c.dim}[${httpTs()}] →${c.reset} ${command}`);
885
+ const result = await runner.run(command);
886
+ const ms = Date.now() - t0;
887
+ const mark = result.isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
888
+ log(`${c.dim}[${httpTs()}]${c.reset} ${mark} ${command} ${c.dim}(${ms}ms)${c.reset}`);
889
+ res.writeHead(200, { 'Content-Type': 'application/json' });
890
+ res.end(JSON.stringify(result));
891
+ }
892
+ catch (e) {
893
+ log(`${c.dim}[${httpTs()}]${c.reset} ${c.red}✗${c.reset} ${command} ${c.dim}— ${e.message}${c.reset}`);
894
+ res.writeHead(500, { 'Content-Type': 'application/json' });
895
+ res.end(JSON.stringify({ text: e.message, isError: true }));
896
+ }
897
+ return;
898
+ }
899
+ if (req.method === 'POST' && req.url === '/run-script') {
900
+ const t0 = Date.now();
901
+ let language = 'javascript';
902
+ try {
903
+ const body = await readBody(req);
904
+ const parsed = JSON.parse(body);
905
+ const script = parsed.script;
906
+ language = parsed.language || 'javascript';
907
+ const preview = String(script).split('\n')[0].slice(0, 60);
908
+ log(`${c.dim}[${httpTs()}] → [${language}]${c.reset} ${preview}${String(script).length > preview.length ? '…' : ''}`);
909
+ const result = runner.runScript
910
+ ? await runner.runScript(script, language)
911
+ : await runner.run(script);
912
+ const ms = Date.now() - t0;
913
+ const mark = result.isError ? `${c.red}✗${c.reset}` : `${c.green}✓${c.reset}`;
914
+ log(`${c.dim}[${httpTs()}]${c.reset} ${mark} [${language}] ${c.dim}(${ms}ms)${c.reset}`);
915
+ res.writeHead(200, { 'Content-Type': 'application/json' });
916
+ res.end(JSON.stringify(result));
917
+ }
918
+ catch (e) {
919
+ log(`${c.dim}[${httpTs()}]${c.reset} ${c.red}✗${c.reset} [${language}] ${c.dim}— ${e.message}${c.reset}`);
920
+ res.writeHead(500, { 'Content-Type': 'application/json' });
921
+ res.end(JSON.stringify({ text: e.message, isError: true }));
922
+ }
923
+ return;
924
+ }
925
+ res.writeHead(404);
926
+ res.end('Not found');
927
+ });
928
+ server.listen(port, () => {
929
+ log(`${c.green}✓${c.reset} HTTP server on port ${port}`);
930
+ resolve(server);
931
+ });
932
+ server.on('error', reject);
933
+ });
934
+ }
935
+ function readBody(req) {
936
+ return new Promise((resolve, reject) => {
937
+ let data = '';
938
+ req.on('data', (chunk) => data += chunk);
939
+ req.on('end', () => resolve(data));
940
+ req.on('error', reject);
941
+ });
942
+ }
943
+ /** Block until SIGINT/SIGTERM. Used by --http console mode (no readline loop). */
944
+ function waitForShutdown(log) {
945
+ return new Promise((resolve) => {
946
+ const onSignal = (sig) => {
947
+ log(`${c.dim}Received ${sig}, shutting down...${c.reset}`);
948
+ resolve();
949
+ };
950
+ process.once('SIGINT', () => onSignal('SIGINT'));
951
+ process.once('SIGTERM', () => onSignal('SIGTERM'));
952
+ });
953
+ }
954
+ /** Try to send --command via HTTP to an already-running --http server. */
955
+ async function tryHttpCommand(command, port) {
956
+ try {
957
+ const result = await new Promise((resolve, reject) => {
958
+ const body = JSON.stringify({ command });
959
+ const req = http.request({ hostname: '127.0.0.1', port, path: '/run', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) } }, res => {
960
+ let data = '';
961
+ res.on('data', (chunk) => data += chunk);
962
+ res.on('end', () => { try {
963
+ resolve(JSON.parse(data));
964
+ }
965
+ catch {
966
+ reject(new Error('Invalid response'));
967
+ } });
968
+ });
969
+ req.on('error', reject);
970
+ req.write(body);
971
+ req.end();
972
+ });
973
+ const text = (result.text ?? '').replace(/^\s*\n+|\n+\s*$/g, '');
974
+ if (text)
975
+ process.stdout.write(text + '\n');
976
+ process.exit(result.isError ? 1 : 0);
977
+ }
978
+ catch (e) {
979
+ const code = e.code;
980
+ if (code === 'ECONNREFUSED' || code === 'ENOTFOUND')
981
+ return false; // Server not running, fall back
982
+ // Server is running but request errored — surface the real error, don't pretend the server is down
983
+ console.error(`HTTP request failed: ${e.message}`);
984
+ process.exit(1);
985
+ }
986
+ return false; // unreachable
987
+ }
861
988
  // ─── REPL ────────────────────────────────────────────────────────────────────
862
989
  export async function startRepl(opts = {}) {
863
990
  const silent = opts.silent || false;
864
991
  const log = (...args) => { if (!silent)
865
992
  console.log(...args); };
993
+ // ─── --command --http: send via HTTP to running server ──────────
994
+ if (opts.command && opts.http) {
995
+ const httpPort = opts.httpPort ?? DEFAULT_HTTP_PORT;
996
+ const sent = await tryHttpCommand(opts.command, httpPort);
997
+ if (sent)
998
+ return;
999
+ console.error(`Could not connect to HTTP server on port ${httpPort}. Is playwright-repl --http running?`);
1000
+ process.exit(1);
1001
+ }
866
1002
  log(`${c.bold}${c.magenta}🎭 Playwright REPL${c.reset} ${c.dim}v${replVersion}${c.reset}`);
867
1003
  // ─── Standalone mode (new: serviceWorker.evaluate) ─────────────
868
1004
  if (!opts.bridge && !opts.connect && !opts.engine) {
@@ -876,6 +1012,28 @@ export async function startRepl(opts = {}) {
876
1012
  // Default to headed for evaluate mode (interactive REPL with extension)
877
1013
  await conn.start(extPath, { headed: opts.headed ?? true, chromium });
878
1014
  log(`${c.green}✓${c.reset} Browser ready (with extension)`);
1015
+ if (opts.command) {
1016
+ const result = await conn.run(opts.command);
1017
+ process.stdout.write((result.text ?? '') + '\n');
1018
+ await conn.close();
1019
+ process.exit(result.isError ? 1 : 0);
1020
+ }
1021
+ // Start HTTP server if --http
1022
+ if (opts.http) {
1023
+ try {
1024
+ await startHttpServer(opts.httpPort ?? DEFAULT_HTTP_PORT, conn, log);
1025
+ }
1026
+ catch (e) {
1027
+ log(`${c.yellow}⚠${c.reset} HTTP server failed: ${e.message}`);
1028
+ }
1029
+ }
1030
+ // --http runs as a console (no readline) unless --interactive is also set
1031
+ if (opts.http && !opts.interactive && !(opts.replay && opts.replay.length > 0)) {
1032
+ log(`${c.dim}Console mode — send commands via HTTP. Ctrl+C to exit.${c.reset}\n`);
1033
+ await waitForShutdown(log);
1034
+ await conn.close();
1035
+ process.exit(0);
1036
+ }
879
1037
  log(`${c.dim}Type .help for commands, JavaScript supported${c.reset}\n`);
880
1038
  if (opts.replay && opts.replay.length > 0) {
881
1039
  await runBridgeReplayMode(opts, conn);
@@ -895,11 +1053,33 @@ export async function startRepl(opts = {}) {
895
1053
  if (opts.bridge) {
896
1054
  const port = opts.bridgePort ?? 9876;
897
1055
  const srv = new BridgeServer();
898
- await srv.start(port);
1056
+ await srv.start(port, { silent: !!opts.command });
899
1057
  log(`Bridge server listening on ws://localhost:${port}`);
1058
+ if (opts.command) {
1059
+ await srv.waitForConnection(30000);
1060
+ const result = await srv.run(opts.command);
1061
+ process.stdout.write((result.text ?? '') + '\n');
1062
+ srv.close();
1063
+ process.exit(result.isError ? 1 : 0);
1064
+ }
1065
+ // Start HTTP server if --http
1066
+ if (opts.http) {
1067
+ try {
1068
+ await startHttpServer(opts.httpPort ?? DEFAULT_HTTP_PORT, srv, log);
1069
+ }
1070
+ catch (e) {
1071
+ log(`${c.yellow}⚠${c.reset} HTTP server failed: ${e.message}`);
1072
+ }
1073
+ }
900
1074
  if (opts.replay && opts.replay.length > 0) {
901
1075
  await runBridgeReplayMode(opts, srv);
902
1076
  }
1077
+ else if (opts.http && !opts.interactive) {
1078
+ log(`${c.dim}Console mode — send commands via HTTP. Ctrl+C to exit.${c.reset}\n`);
1079
+ await waitForShutdown(log);
1080
+ srv.close();
1081
+ process.exit(0);
1082
+ }
903
1083
  else {
904
1084
  log('Waiting for extension to connect...');
905
1085
  await startBridgeLoop(opts, srv);
@@ -917,6 +1097,19 @@ export async function startRepl(opts = {}) {
917
1097
  console.error(`${c.red}✗${c.reset} Failed to start: ${err.message}`);
918
1098
  process.exit(1);
919
1099
  }
1100
+ if (opts.command) {
1101
+ const parsed = parseInput(opts.command);
1102
+ if (!parsed) {
1103
+ process.stderr.write('Invalid command\n');
1104
+ conn.close();
1105
+ process.exit(1);
1106
+ return; // unreachable, but satisfies TS control-flow
1107
+ }
1108
+ const result = await conn.run(parsed);
1109
+ process.stdout.write((result.text ?? '') + '\n');
1110
+ conn.close();
1111
+ process.exit(result.isError ? 1 : 0);
1112
+ }
920
1113
  // ─── Session + readline ──────────────────────────────────────────
921
1114
  const session = new SessionManager();
922
1115
  const historyDir = path.join(os.homedir(), '.playwright-repl');
@@ -949,4 +1142,3 @@ export async function startRepl(opts = {}) {
949
1142
  startCommandLoop(ctx);
950
1143
  }
951
1144
  }
952
- //# sourceMappingURL=repl.js.map
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "playwright-repl",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "description": "Interactive REPL for Playwright — keyword commands, recording, and replay",
5
5
  "type": "module",
6
6
  "bin": {
7
- "playwright-repl": "./dist/playwright-repl.js"
7
+ "playwright-repl": "./dist/playwright-repl.js",
8
+ "pw-cli": "./dist/pw-cli.js"
8
9
  },
9
10
  "main": "./dist/index.js",
10
11
  "exports": {
@@ -35,8 +36,8 @@
35
36
  "dependencies": {
36
37
  "playwright": "^1.59.1",
37
38
  "playwright-core": "^1.59.1",
38
- "@playwright-repl/browser-extension": "0.24.0",
39
- "@playwright-repl/core": "0.24.0"
39
+ "@playwright-repl/browser-extension": "0.25.0",
40
+ "@playwright-repl/core": "0.25.0"
40
41
  },
41
42
  "devDependencies": {
42
43
  "vitest": "^4.0.18"
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-client.d.ts","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAI1D,qBAAa,aAAa;IACtB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,SAAS,CAAuB;IACxC,OAAO,CAAC,UAAU,CAAS;gBAEf,GAAG,CAAC,EAAE,MAAM;IAIxB,IAAI,SAAS,IAAI,OAAO,CAA4B;IAE9C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BtB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,eAAe,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,OAAO,CAAC,YAAY,CAAC;IAUjF,SAAS,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAE,IAAI,GAAG,YAAmB,GAAG,OAAO,CAAC,YAAY,CAAC;IAQtF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAW5B,SAAS,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,IAAI;IAChC,YAAY,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,IAAI;IACnC,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,IAAI;YAE9C,QAAQ;YAoCR,IAAI;IAIlB,OAAO,CAAC,OAAO;CAwDlB"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"http-client.js","sourceRoot":"","sources":["../src/http-client.ts"],"names":[],"mappings":"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"}