@romanmatena/browsermonitor 2.0.1 → 2.1.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/README.md +11 -10
- package/package.json +1 -1
- package/src/agents.llm/browser-monitor-section.md +6 -4
- package/src/cli.mjs +103 -105
- package/src/http-server.mjs +12 -38
- package/src/init.mjs +48 -103
- package/src/logging/LogBuffer.mjs +1 -1
- package/src/monitor/README.md +18 -5
- package/src/monitor/join-mode.mjs +127 -335
- package/src/monitor/open-mode.mjs +141 -384
- package/src/monitor/shared/cleanup.mjs +96 -0
- package/src/monitor/shared/help.mjs +64 -0
- package/src/monitor/shared/http-state-setup.mjs +55 -0
- package/src/monitor/shared/index.mjs +11 -0
- package/src/monitor/shared/keyboard-handler.mjs +79 -0
- package/src/monitor/shared/monitoring-wrapper.mjs +39 -0
- package/src/monitor/shared/tab-switching.mjs +61 -0
- package/src/monitor/shared/user-page-filter.mjs +28 -0
- package/src/os/wsl/chrome.mjs +3 -36
- package/src/os/wsl/detect.mjs +0 -12
- package/src/os/wsl/index.mjs +0 -3
- package/src/settings.mjs +12 -10
- package/src/templates/api-help.mjs +9 -11
- package/src/templates/cli-commands.mjs +1 -1
- package/src/templates/section-heading.mjs +10 -16
- package/src/utils/ask.mjs +94 -28
- package/src/utils/chrome-instances.mjs +81 -0
- package/src/utils/chrome-profile-path.mjs +9 -4
- package/src/utils/status-line.mjs +0 -8
- package/src/monitor/index.mjs +0 -18
- package/src/monitor/interactive-mode.mjs +0 -275
- package/src/monitor.mjs +0 -39
package/README.md
CHANGED
|
@@ -58,9 +58,9 @@ pnpm add -g @romanmatena/browsermonitor
|
|
|
58
58
|
|
|
59
59
|
**Note:** Chromium download is skipped — browsermonitor uses your system Chrome/Chromium. No extra 300 MB download.
|
|
60
60
|
|
|
61
|
-
**First run:** When you run `browsermonitor` in a project directory
|
|
61
|
+
**First run (interactive):** When you run `browsermonitor` for the first time in a project directory, interactive mode asks for HTTP API port, saves `settings.json`, and updates agent files (`CLAUDE.md`, `AGENTS.md`, `memory.md`). When you press `o` (open), it asks for the default URL.
|
|
62
62
|
|
|
63
|
-
**
|
|
63
|
+
**Re-init:** Run `browsermonitor init` to recreate settings and update agent files.
|
|
64
64
|
|
|
65
65
|
## Quick Start
|
|
66
66
|
|
|
@@ -74,7 +74,7 @@ browsermonitor --join=9222 # Join mode: attach to existing Chrome on port
|
|
|
74
74
|
|
|
75
75
|
| Mode | How to run | When to use |
|
|
76
76
|
|------------|--------------------------|-------------|
|
|
77
|
-
| **Interactive** | `browsermonitor` (no flags) |
|
|
77
|
+
| **Interactive** | `browsermonitor` (no flags) | First run asks HTTP port. Then menu: **o** = open Chrome (asks URL on first use), **j** = join running Chrome, **q** = quit. |
|
|
78
78
|
| **Open** | `browsermonitor --open [url]` | Launch a new Chrome and monitor it. Uses current dir for logs. |
|
|
79
79
|
| **Join** | `browsermonitor --join=PORT` | Attach to an existing Chrome with remote debugging on PORT (e.g. 9222). Port is required. |
|
|
80
80
|
|
|
@@ -132,7 +132,7 @@ For **join mode** (attach to existing Chrome): start Chrome manually with `--rem
|
|
|
132
132
|
|
|
133
133
|
Each project gets its own Chrome profile:
|
|
134
134
|
- **WSL:** `%LOCALAPPDATA%\browsermonitor\{project}_{hash}` (Windows path)
|
|
135
|
-
- **Native:** `.browsermonitor-profile/` in project dir
|
|
135
|
+
- **Native:** `.browsermonitor/.chrome-profile/` in project dir
|
|
136
136
|
|
|
137
137
|
Separate cookies and logins per project; won't interfere with your regular Chrome.
|
|
138
138
|
|
|
@@ -172,8 +172,8 @@ When browsermonitor runs in a project directory, it creates:
|
|
|
172
172
|
│ ├── cookies/ # Per-domain cookie JSONs
|
|
173
173
|
│ ├── dom.html
|
|
174
174
|
│ └── screenshot.png
|
|
175
|
-
└── .
|
|
176
|
-
|
|
175
|
+
│ └── .chrome-profile/ # Chrome profile (native) or
|
|
176
|
+
│ # %LOCALAPPDATA%\browsermonitor\ (WSL)
|
|
177
177
|
```
|
|
178
178
|
|
|
179
179
|
## Keyboard Controls (open/join mode)
|
|
@@ -208,8 +208,8 @@ Use `curl` to communicate with the HTTP API over REST. Default URL: `http://loca
|
|
|
208
208
|
|
|
209
209
|
| Endpoint | Description |
|
|
210
210
|
|----------|-------------|
|
|
211
|
-
| `GET /dump` | Dump logs, DOM, cookies, screenshot to files; returns output paths |
|
|
212
|
-
| `GET /status` | Current status, monitored URLs,
|
|
211
|
+
| `GET /dump` | Dump logs, DOM, cookies, screenshot to files; returns output file paths |
|
|
212
|
+
| `GET /status` | Current state: status, mode, monitored URLs, collecting flag, stats |
|
|
213
213
|
| `GET /stop` | Pause collecting (console/network) |
|
|
214
214
|
| `GET /start` | Resume collecting |
|
|
215
215
|
| `GET /clear` | Clear in-memory buffers |
|
|
@@ -341,7 +341,7 @@ Second launch: chrome.exe --remote-debugging-port=9223 --user-data-dir=Y
|
|
|
341
341
|
|
|
342
342
|
**Consequence:** If user has Chrome open for regular browsing, launching a new Chrome with debug flags does nothing - the existing Chrome (without debugging) handles it.
|
|
343
343
|
|
|
344
|
-
**Detection method (used in
|
|
344
|
+
**Detection method (used in open-mode.mjs):**
|
|
345
345
|
```bash
|
|
346
346
|
# From WSL, query Windows WMI for Chrome processes:
|
|
347
347
|
wmic.exe process where "name='chrome.exe'" get processid,commandline
|
|
@@ -553,6 +553,7 @@ netstat -ano | findstr "9222.*LISTEN"
|
|
|
553
553
|
|
|
554
554
|
- **Entry point:** [cli.mjs](src/cli.mjs) – argument parsing, mode dispatch
|
|
555
555
|
- **Settings:** [settings.mjs](src/settings.mjs) – project paths, config loading
|
|
556
|
-
- **Modes:** [monitor
|
|
556
|
+
- **Modes:** [monitor/open-mode.mjs](src/monitor/open-mode.mjs), [monitor/join-mode.mjs](src/monitor/join-mode.mjs)
|
|
557
|
+
- **Shared modules:** [monitor/shared/](src/monitor/shared/) – cleanup, keyboard, HTTP state, tab switching, help, monitoring wrapper, user page filter
|
|
557
558
|
- **WSL:** [os/wsl/index.mjs](src/os/wsl/index.mjs) – `runWslDiagnostics()`, `scanChromeInstances()`, Chrome launch and port proxy helpers
|
|
558
559
|
- **Chrome launch (WSL):** [os/wsl/chrome.mjs](src/os/wsl/chrome.mjs) – `startChromeOnWindows()`, port proxy, profile path
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@romanmatena/browsermonitor",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Browser console, network, DOM monitoring for debugging and LLM workflows. Captures console, network, cookies, screenshot, DOM. HTTP API for LLM/scripts. WSL+Windows, Linux, Open/Join modes.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
|
|
4
4
|
**Why use this:** Do not ask the user to copy or paste content from the browser (DOM, console, network). You can obtain the current page state yourself: the project has browsermonitor; after a dump you read the generated files.
|
|
5
5
|
|
|
6
|
+
**Monitored URL:** `{{DEFAULT_URL}}`
|
|
7
|
+
|
|
6
8
|
**What to do when you need the live page (elements, console, requests):**
|
|
7
|
-
1. Trigger a dump: `curl -s http://localhost:
|
|
9
|
+
1. Trigger a dump: `curl -s http://localhost:{{HTTP_PORT}}/dump` (monitor must be running; user runs `browsermonitor` or `browsermonitor --open` / `--join=9222` to start it).
|
|
8
10
|
2. Read the written files in `.browsermonitor/.puppeteer/`:
|
|
9
11
|
- **.browsermonitor/.puppeteer/dom.html** – current page HTML (JS-modified DOM). Use this for the element tree and structure.
|
|
10
12
|
- **.browsermonitor/.puppeteer/screenshot.png** – screenshot of the current tab viewport.
|
|
11
13
|
- **.browsermonitor/.puppeteer/console.log** – browser console output.
|
|
12
14
|
- **.browsermonitor/.puppeteer/network.log** and **.browsermonitor/.puppeteer/network-log/*.json** – network requests and responses.
|
|
13
15
|
- **.browsermonitor/.puppeteer/cookies/** – cookies per domain (JSON files).
|
|
14
|
-
3. Optional: `curl -s http://localhost:
|
|
15
|
-
4. Optional: `curl -s http://localhost:
|
|
16
|
-
5. Optional: `curl -s http://localhost:
|
|
16
|
+
3. Optional: `curl -s http://localhost:{{HTTP_PORT}}/status` for buffer status.
|
|
17
|
+
4. Optional: `curl -s http://localhost:{{HTTP_PORT}}/tabs` to list tabs; `curl -s "http://localhost:{{HTTP_PORT}}/tab?index=2"` to switch tab.
|
|
18
|
+
5. Optional: `curl -s http://localhost:{{HTTP_PORT}}/clear` to clear buffers.
|
|
17
19
|
|
|
18
20
|
<!-- END browser-monitor-llm-section - LLM: do not remove this block; required for automatic section updates -->
|
package/src/cli.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* Configuration in .browsermonitor/settings.json (created on first run).
|
|
7
7
|
*
|
|
8
8
|
* Subcommands:
|
|
9
|
-
* init →
|
|
9
|
+
* init → Create .browsermonitor/, settings.json, update agent files
|
|
10
10
|
*
|
|
11
11
|
* Mode is chosen by arguments:
|
|
12
12
|
* (none) → Interactive: menu (o = open, j = join, q = quit)
|
|
@@ -21,29 +21,49 @@
|
|
|
21
21
|
* --help Show help
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import {
|
|
24
|
+
import { parseArgs } from 'node:util';
|
|
25
|
+
import { runJoinMode } from './monitor/join-mode.mjs';
|
|
26
|
+
import { runOpenMode } from './monitor/open-mode.mjs';
|
|
25
27
|
import { printAppIntro } from './intro.mjs';
|
|
26
28
|
import { createHttpServer } from './http-server.mjs';
|
|
27
29
|
import { printApiHelpTable } from './templates/api-help.mjs';
|
|
28
30
|
import { printCliCommandsTable } from './templates/cli-commands.mjs';
|
|
29
|
-
import {
|
|
30
|
-
import { loadSettings, isInitialized,
|
|
31
|
+
import { printModeHeading } from './templates/section-heading.mjs';
|
|
32
|
+
import { loadSettings, getPaths, ensureDirectories, isInitialized, DEFAULT_SETTINGS, saveSettings } from './settings.mjs';
|
|
31
33
|
import { runInit } from './init.mjs';
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
34
|
+
import { resolveHttpPort, resolveDefaultUrl, askMode } from './utils/ask.mjs';
|
|
35
|
+
|
|
36
|
+
// ---- Parse CLI arguments ----
|
|
37
|
+
const { values: flags, positionals } = parseArgs({
|
|
38
|
+
args: process.argv.slice(2),
|
|
39
|
+
options: {
|
|
40
|
+
open: { type: 'boolean', default: false },
|
|
41
|
+
join: { type: 'string' },
|
|
42
|
+
port: { type: 'string' },
|
|
43
|
+
realtime: { type: 'boolean', default: false },
|
|
44
|
+
headless: { type: 'boolean', default: false },
|
|
45
|
+
timeout: { type: 'string', default: '0' },
|
|
46
|
+
'nav-timeout': { type: 'string' },
|
|
47
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
48
|
+
},
|
|
49
|
+
allowPositionals: true,
|
|
50
|
+
strict: false,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const subcommand = positionals[0];
|
|
54
|
+
const urlFromArgs = positionals.find((a) => /^https?:\/\//.test(a) || a.includes('localhost'));
|
|
55
|
+
const joinPort = flags.join ? parseInt(flags.join, 10) : null;
|
|
56
|
+
const httpPortFromArgs = flags.port ? parseInt(flags.port, 10) : null;
|
|
57
|
+
const hardTimeout = parseInt(flags.timeout, 10) || 0;
|
|
58
|
+
|
|
59
|
+
// Handle `browsermonitor init`
|
|
60
|
+
if (subcommand === 'init') {
|
|
61
|
+
await runInit(process.cwd());
|
|
42
62
|
process.exit(0);
|
|
43
63
|
}
|
|
44
64
|
|
|
45
65
|
// Show help
|
|
46
|
-
if (
|
|
66
|
+
if (flags.help) {
|
|
47
67
|
console.log(`
|
|
48
68
|
Browser Monitor – capture browser console, network, and DOM for debugging and LLM workflows.
|
|
49
69
|
|
|
@@ -57,29 +77,23 @@ What it does:
|
|
|
57
77
|
printCliCommandsTable({ showEntry: true, showUsage: true });
|
|
58
78
|
console.log(`
|
|
59
79
|
Subcommands:
|
|
60
|
-
init
|
|
80
|
+
init Create .browsermonitor/, settings.json with defaults, update agent files
|
|
61
81
|
|
|
62
82
|
Modes (chosen by flags; only one applies):
|
|
63
|
-
INTERACTIVE (default) No flag.
|
|
64
|
-
o = open Chrome
|
|
65
|
-
j = join running Chrome (pick instance/tab)
|
|
66
|
-
q = quit
|
|
83
|
+
INTERACTIVE (default) No flag. First run asks HTTP port and URL. Then menu:
|
|
84
|
+
o = open Chrome, j = join running Chrome, q = quit
|
|
67
85
|
|
|
68
86
|
OPEN (--open) Launch a new Chrome and monitor it. URL = first positional or config.
|
|
69
|
-
Uses current directory for logs. Good for local dev with a fresh profile.
|
|
70
87
|
|
|
71
|
-
JOIN (--join=PORT) Attach to
|
|
72
|
-
|
|
73
|
-
running (e.g. started by a script or on another machine via tunnel).
|
|
88
|
+
JOIN (--join=PORT) Attach to existing Chrome with remote debugging on PORT.
|
|
89
|
+
If PORT omitted, scans for running instances.
|
|
74
90
|
|
|
75
91
|
Options:
|
|
76
92
|
--port=PORT HTTP API port (default: from settings or 60001)
|
|
77
|
-
--realtime Write each event to files immediately (default: lazy
|
|
93
|
+
--realtime Write each event to files immediately (default: lazy)
|
|
78
94
|
--headless Run Chrome without GUI
|
|
79
|
-
--open Go directly to open mode
|
|
80
|
-
--join=PORT Go directly to join mode (PORT required)
|
|
81
95
|
--timeout=MS Hard timeout in ms; process exits after (0 = disabled)
|
|
82
|
-
--nav-timeout=MS Navigation timeout in ms (default: from settings
|
|
96
|
+
--nav-timeout=MS Navigation timeout in ms (default: from settings)
|
|
83
97
|
--help, -h Show this help
|
|
84
98
|
|
|
85
99
|
Config (.browsermonitor/settings.json):
|
|
@@ -90,67 +104,42 @@ Config (.browsermonitor/settings.json):
|
|
|
90
104
|
process.exit(0);
|
|
91
105
|
}
|
|
92
106
|
|
|
93
|
-
// Auto-init on first run
|
|
94
|
-
if (!isInitialized(projectRoot)) {
|
|
95
|
-
console.log('[browsermonitor] First run detected. Setting up .browsermonitor/ ...');
|
|
96
|
-
await runInit(projectRoot, { askForUrl: process.stdin.isTTY, updateAgentFiles: true });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Ensure directories exist (in case user deleted .puppeteer/ subdir)
|
|
100
|
-
ensureDirectories(projectRoot);
|
|
101
|
-
|
|
102
|
-
// Load settings from .browsermonitor/settings.json
|
|
103
|
-
const config = loadSettings(projectRoot);
|
|
104
|
-
const paths = getPaths(projectRoot);
|
|
105
|
-
|
|
106
|
-
// ---- Mode dispatch: --open | --join=PORT | interactive ----
|
|
107
|
-
const openMode = args.some((a) => a === '--open' || a.startsWith('--open='));
|
|
108
|
-
const joinArg = args.find((a) => a.startsWith('--join'));
|
|
109
|
-
let joinPort = null;
|
|
110
|
-
if (joinArg) {
|
|
111
|
-
if (joinArg === '--join' || !joinArg.includes('=')) {
|
|
112
|
-
console.error('Error: --join requires a port (e.g. --join=9222)');
|
|
113
|
-
process.exit(1);
|
|
114
|
-
}
|
|
115
|
-
const portStr = joinArg.split('=')[1];
|
|
116
|
-
joinPort = parseInt(portStr, 10);
|
|
117
|
-
if (Number.isNaN(joinPort) || joinPort < 1 || joinPort > 65535) {
|
|
118
|
-
console.error(`Error: invalid port for --join: ${portStr}`);
|
|
119
|
-
process.exit(1);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Shared options (CLI args override settings.json)
|
|
124
|
-
const realtimeMode = args.includes('--realtime') || config.realtime;
|
|
125
|
-
const headlessCli = args.includes('--headless');
|
|
126
|
-
const timeoutArg = args.find((a) => a.startsWith('--timeout='));
|
|
127
|
-
const hardTimeout = timeoutArg ? parseInt(timeoutArg.split('=')[1], 10) : 0;
|
|
128
|
-
const navTimeoutArg = args.find((a) => a.startsWith('--nav-timeout='));
|
|
129
|
-
const navigationTimeout = navTimeoutArg
|
|
130
|
-
? parseInt(navTimeoutArg.split('=')[1], 10)
|
|
131
|
-
: (config.navigationTimeout !== undefined ? config.navigationTimeout : 60_000);
|
|
132
|
-
const urlFromArgs = args.find((a) => !a.startsWith('--'));
|
|
133
|
-
const url = urlFromArgs || config.defaultUrl || 'https://localhost:4000/';
|
|
134
|
-
const headless = headlessCli || config.headless || false;
|
|
135
|
-
const ignorePatterns = config.ignorePatterns || [];
|
|
136
|
-
const outputDir = projectRoot;
|
|
137
|
-
|
|
138
|
-
const DEFAULT_HTTP_PORT = config.httpPort || 60001;
|
|
139
|
-
const portArg = args.find((a) => a === '--port' || a.startsWith('--port='));
|
|
140
|
-
let httpPortFromArgs = null;
|
|
141
|
-
if (portArg) {
|
|
142
|
-
const val = portArg.includes('=') ? portArg.split('=')[1] : '';
|
|
143
|
-
const num = parseInt(val, 10);
|
|
144
|
-
if (!Number.isNaN(num) && num >= 1 && num <= 65535) httpPortFromArgs = num;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
107
|
(async () => {
|
|
108
|
+
// 1. Intro
|
|
148
109
|
printAppIntro();
|
|
149
110
|
|
|
150
|
-
|
|
111
|
+
// 2. Project root = cwd, load existing settings (may be empty/missing)
|
|
112
|
+
const projectRoot = process.cwd();
|
|
113
|
+
ensureDirectories(projectRoot);
|
|
114
|
+
let config = loadSettings(projectRoot);
|
|
115
|
+
const paths = getPaths(projectRoot);
|
|
116
|
+
|
|
117
|
+
// 3. CLI args override settings.json
|
|
118
|
+
const realtimeMode = flags.realtime || config.realtime;
|
|
119
|
+
const navTimeoutFromArgs = flags['nav-timeout'] ? parseInt(flags['nav-timeout'], 10) : null;
|
|
120
|
+
const navigationTimeout = navTimeoutFromArgs
|
|
121
|
+
?? (config.navigationTimeout !== undefined ? config.navigationTimeout : 60_000);
|
|
122
|
+
const headless = flags.headless || config.headless || false;
|
|
123
|
+
const httpPort = await resolveHttpPort(httpPortFromArgs ?? config.httpPort, DEFAULT_SETTINGS.httpPort);
|
|
124
|
+
const url = await resolveDefaultUrl(urlFromArgs || config.defaultUrl, DEFAULT_SETTINGS.defaultUrl);
|
|
125
|
+
|
|
126
|
+
// 4. Need to initialize project (create .browsermonitor/, settings.json) before showing API info, because API port is part of config
|
|
127
|
+
if (!isInitialized(projectRoot)) {
|
|
128
|
+
saveSettings(projectRoot, { ...DEFAULT_SETTINGS, httpPort, defaultUrl: url, headless, navigationTimeout, realtime: realtimeMode });
|
|
129
|
+
config = loadSettings(projectRoot);
|
|
130
|
+
await runInit(projectRoot, config);
|
|
131
|
+
}
|
|
151
132
|
|
|
152
|
-
|
|
153
|
-
|
|
133
|
+
// 5. Show API/output info (now httpPort is known)
|
|
134
|
+
printApiHelpTable({
|
|
135
|
+
url: config.defaultUrl,
|
|
136
|
+
port: config.httpPort,
|
|
137
|
+
showApi: true,
|
|
138
|
+
showInteractive: false,
|
|
139
|
+
showOutputFiles: true,
|
|
140
|
+
noLeadingNewline: true,
|
|
141
|
+
context: paths,
|
|
142
|
+
});
|
|
154
143
|
|
|
155
144
|
const sharedHttpState = {
|
|
156
145
|
mode: 'interactive',
|
|
@@ -162,41 +151,50 @@ if (portArg) {
|
|
|
162
151
|
getAllTabs: async () => [],
|
|
163
152
|
};
|
|
164
153
|
const sharedHttpServer = createHttpServer({
|
|
165
|
-
port: httpPort,
|
|
166
|
-
defaultPort:
|
|
154
|
+
port: config.httpPort,
|
|
155
|
+
defaultPort: config.httpPort,
|
|
167
156
|
getState: () => sharedHttpState,
|
|
168
157
|
});
|
|
169
158
|
|
|
170
159
|
const commonOptions = {
|
|
171
|
-
|
|
172
|
-
outputDir,
|
|
160
|
+
outputDir: projectRoot,
|
|
173
161
|
paths,
|
|
174
|
-
|
|
162
|
+
realtime: realtimeMode,
|
|
163
|
+
ignorePatterns: config.ignorePatterns,
|
|
175
164
|
hardTimeout,
|
|
176
|
-
httpPort,
|
|
165
|
+
httpPort: config.httpPort,
|
|
166
|
+
joinPort,
|
|
177
167
|
sharedHttpState,
|
|
178
168
|
sharedHttpServer,
|
|
179
169
|
};
|
|
180
170
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
await runOpenMode(
|
|
171
|
+
// 8. Dispatch to mode
|
|
172
|
+
if (flags.open) {
|
|
173
|
+
await runOpenMode(config.defaultUrl, {
|
|
184
174
|
...commonOptions,
|
|
185
175
|
headless,
|
|
186
176
|
navigationTimeout,
|
|
187
177
|
});
|
|
188
178
|
} else if (joinPort !== null) {
|
|
189
|
-
|
|
190
|
-
await runJoinMode(joinPort, {
|
|
191
|
-
...commonOptions,
|
|
192
|
-
defaultUrl: url,
|
|
193
|
-
});
|
|
179
|
+
await runJoinMode(config.defaultUrl, commonOptions);
|
|
194
180
|
} else {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
181
|
+
// No mode flag → ask user
|
|
182
|
+
printModeHeading('Choose mode');
|
|
183
|
+
const mode = await askMode();
|
|
184
|
+
if (mode === 'q') process.exit(0);
|
|
185
|
+
|
|
186
|
+
if (mode === 'o') {
|
|
187
|
+
await runOpenMode(config.defaultUrl, {
|
|
188
|
+
...commonOptions,
|
|
189
|
+
headless,
|
|
190
|
+
navigationTimeout,
|
|
191
|
+
skipModeHeading: true,
|
|
192
|
+
});
|
|
193
|
+
} else if (mode === 'j') {
|
|
194
|
+
await runJoinMode(config.defaultUrl, {
|
|
195
|
+
...commonOptions,
|
|
196
|
+
skipModeHeading: true,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
201
199
|
}
|
|
202
200
|
})();
|
package/src/http-server.mjs
CHANGED
|
@@ -12,8 +12,8 @@ import http from 'http';
|
|
|
12
12
|
import { C, log } from './utils/colors.mjs';
|
|
13
13
|
import { getFullTimestamp } from './logging/index.mjs';
|
|
14
14
|
import { getComputedStylesFromPage } from './logging/dump.mjs';
|
|
15
|
-
import {
|
|
16
|
-
|
|
15
|
+
import { API_ENDPOINTS } from './templates/api-help.mjs';
|
|
16
|
+
|
|
17
17
|
|
|
18
18
|
/** Default timeout for Puppeteer operations (ms). */
|
|
19
19
|
const PUPPETEER_CALL_TIMEOUT_MS = 30_000;
|
|
@@ -155,7 +155,6 @@ export function createHttpServer(options) {
|
|
|
155
155
|
return;
|
|
156
156
|
}
|
|
157
157
|
try {
|
|
158
|
-
const statsBeforeDump = s.logBuffer.getStats();
|
|
159
158
|
const pages = s.getPages();
|
|
160
159
|
const page = pages.length > 0 ? pages[0] : null;
|
|
161
160
|
|
|
@@ -172,26 +171,14 @@ export function createHttpServer(options) {
|
|
|
172
171
|
success: true,
|
|
173
172
|
timestamp: getFullTimestamp(),
|
|
174
173
|
message: 'Dump completed. Read the files below.',
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
},
|
|
184
|
-
llm: {
|
|
185
|
-
instruction: 'Read or download these files to get the current browser state. Do not ask the user to copy/paste from the browser.',
|
|
186
|
-
files: [
|
|
187
|
-
{ path: s.logBuffer.DOM_HTML, what: 'Current page HTML (JS-modified DOM). Use for element tree and structure.' },
|
|
188
|
-
{ path: s.logBuffer.SCREENSHOT, what: 'Screenshot of the current tab viewport (PNG).' },
|
|
189
|
-
{ path: s.logBuffer.CONSOLE_LOG, what: 'Browser console output (logs, errors, warnings).' },
|
|
190
|
-
{ path: s.logBuffer.NETWORK_LOG, what: 'Network requests overview (one line per request with ID).' },
|
|
191
|
-
{ path: s.logBuffer.NETWORK_DIR, what: 'Directory with one JSON per request: full headers, payload, response (see IDs in network log).' },
|
|
192
|
-
{ path: s.logBuffer.COOKIES_DIR, what: 'Directory with cookies per domain (JSON files).' },
|
|
193
|
-
],
|
|
194
|
-
},
|
|
174
|
+
files: [
|
|
175
|
+
{ path: s.logBuffer.DOM_HTML, what: 'Current page HTML (JS-modified DOM). Use for element tree and structure.' },
|
|
176
|
+
{ path: s.logBuffer.SCREENSHOT, what: 'Screenshot of the current tab viewport (PNG).' },
|
|
177
|
+
{ path: s.logBuffer.CONSOLE_LOG, what: 'Browser console output (logs, errors, warnings).' },
|
|
178
|
+
{ path: s.logBuffer.NETWORK_LOG, what: 'Network requests overview (one line per request with ID).' },
|
|
179
|
+
{ path: s.logBuffer.NETWORK_DIR, what: 'Directory with one JSON per request: full headers, payload, response (see IDs in network log).' },
|
|
180
|
+
{ path: s.logBuffer.COOKIES_DIR, what: 'Directory with cookies per domain (JSON files).' },
|
|
181
|
+
],
|
|
195
182
|
}, null, 2));
|
|
196
183
|
} catch (error) {
|
|
197
184
|
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
@@ -200,7 +187,7 @@ export function createHttpServer(options) {
|
|
|
200
187
|
return;
|
|
201
188
|
}
|
|
202
189
|
|
|
203
|
-
// GET /status
|
|
190
|
+
// GET /status — state only (no file paths; use GET /dump for that)
|
|
204
191
|
if (req.url === '/status' && req.method === 'GET') {
|
|
205
192
|
const pages = s.getPages();
|
|
206
193
|
const collectingPaused = s.getCollectingPaused();
|
|
@@ -215,14 +202,6 @@ export function createHttpServer(options) {
|
|
|
215
202
|
if (!noBrowser) {
|
|
216
203
|
payload.collecting = collectingPaused ? 'paused' : 'running';
|
|
217
204
|
payload.stats = s.logBuffer.getStats();
|
|
218
|
-
payload.outputFiles = {
|
|
219
|
-
consoleLog: s.logBuffer.CONSOLE_LOG,
|
|
220
|
-
networkLog: s.logBuffer.NETWORK_LOG,
|
|
221
|
-
networkDir: s.logBuffer.NETWORK_DIR,
|
|
222
|
-
cookiesDir: s.logBuffer.COOKIES_DIR,
|
|
223
|
-
domHtml: s.logBuffer.DOM_HTML,
|
|
224
|
-
screenshot: s.logBuffer.SCREENSHOT,
|
|
225
|
-
};
|
|
226
205
|
} else {
|
|
227
206
|
payload.message = 'No browser. Use interactive (o/j) or --open / --join.';
|
|
228
207
|
}
|
|
@@ -500,12 +479,7 @@ export function createHttpServer(options) {
|
|
|
500
479
|
}, null, 2));
|
|
501
480
|
});
|
|
502
481
|
|
|
503
|
-
server.listen(port, host
|
|
504
|
-
const url = `http://${host}:${port}`;
|
|
505
|
-
const changed = port !== defaultPort ? ` ${C.red}(changed)${C.reset}` : '';
|
|
506
|
-
const title = `HTTP API URL: ${url}${changed}`;
|
|
507
|
-
printSectionHeading(title, ' ');
|
|
508
|
-
});
|
|
482
|
+
server.listen(port, host);
|
|
509
483
|
|
|
510
484
|
server.on('error', (err) => {
|
|
511
485
|
if (err.code === 'EADDRINUSE') {
|