gologin-agent-browser-cli 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 +21 -0
- package/README.md +220 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +352 -0
- package/dist/commands/check.d.ts +2 -0
- package/dist/commands/check.js +17 -0
- package/dist/commands/click.d.ts +2 -0
- package/dist/commands/click.js +17 -0
- package/dist/commands/close.d.ts +2 -0
- package/dist/commands/close.js +12 -0
- package/dist/commands/current.d.ts +2 -0
- package/dist/commands/current.js +9 -0
- package/dist/commands/dblclick.d.ts +2 -0
- package/dist/commands/dblclick.js +17 -0
- package/dist/commands/fill.d.ts +2 -0
- package/dist/commands/fill.js +18 -0
- package/dist/commands/find.d.ts +2 -0
- package/dist/commands/find.js +86 -0
- package/dist/commands/focus.d.ts +2 -0
- package/dist/commands/focus.js +17 -0
- package/dist/commands/get.d.ts +2 -0
- package/dist/commands/get.js +19 -0
- package/dist/commands/hover.d.ts +2 -0
- package/dist/commands/hover.js +17 -0
- package/dist/commands/open.d.ts +2 -0
- package/dist/commands/open.js +67 -0
- package/dist/commands/pdf.d.ts +2 -0
- package/dist/commands/pdf.js +18 -0
- package/dist/commands/press.d.ts +2 -0
- package/dist/commands/press.js +19 -0
- package/dist/commands/screenshot.d.ts +2 -0
- package/dist/commands/screenshot.js +20 -0
- package/dist/commands/scroll.d.ts +2 -0
- package/dist/commands/scroll.js +25 -0
- package/dist/commands/scrollIntoView.d.ts +2 -0
- package/dist/commands/scrollIntoView.js +17 -0
- package/dist/commands/select.d.ts +2 -0
- package/dist/commands/select.js +18 -0
- package/dist/commands/sessions.d.ts +2 -0
- package/dist/commands/sessions.js +15 -0
- package/dist/commands/shared.d.ts +3 -0
- package/dist/commands/shared.js +13 -0
- package/dist/commands/snapshot.d.ts +2 -0
- package/dist/commands/snapshot.js +16 -0
- package/dist/commands/type.d.ts +2 -0
- package/dist/commands/type.js +18 -0
- package/dist/commands/uncheck.d.ts +2 -0
- package/dist/commands/uncheck.js +17 -0
- package/dist/commands/upload.d.ts +2 -0
- package/dist/commands/upload.js +18 -0
- package/dist/commands/wait.d.ts +2 -0
- package/dist/commands/wait.js +41 -0
- package/dist/daemon/browser.d.ts +37 -0
- package/dist/daemon/browser.js +557 -0
- package/dist/daemon/refStore.d.ts +9 -0
- package/dist/daemon/refStore.js +26 -0
- package/dist/daemon/server.d.ts +1 -0
- package/dist/daemon/server.js +235 -0
- package/dist/daemon/sessionManager.d.ts +50 -0
- package/dist/daemon/sessionManager.js +512 -0
- package/dist/daemon/snapshot.d.ts +8 -0
- package/dist/daemon/snapshot.js +285 -0
- package/dist/lib/config.d.ts +3 -0
- package/dist/lib/config.js +58 -0
- package/dist/lib/errors.d.ts +12 -0
- package/dist/lib/errors.js +63 -0
- package/dist/lib/types.d.ts +301 -0
- package/dist/lib/types.js +2 -0
- package/dist/lib/utils.d.ts +27 -0
- package/dist/lib/utils.js +165 -0
- package/examples/agent-flow.sh +19 -0
- package/package.json +59 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 GoLogin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
# GoLogin Agent CLI
|
|
2
|
+
|
|
3
|
+
GoLogin Agent CLI is a cloud browser automation CLI built for AI agents. It turns GoLogin Cloud Browser into a persistent, scriptable runtime with compact page snapshots, ref-based interaction, session memory, and shell-friendly commands.
|
|
4
|
+
|
|
5
|
+
It is designed for agent loops that need to stay simple:
|
|
6
|
+
|
|
7
|
+
- open a live browser session
|
|
8
|
+
- read the page as a compact text snapshot
|
|
9
|
+
- act on stable refs like `@e3`
|
|
10
|
+
- keep working across multiple CLI calls through a local daemon
|
|
11
|
+
- save artifacts such as screenshots and PDFs when needed
|
|
12
|
+
|
|
13
|
+
Unlike local-browser automation tools, it runs on top of a cloud browser stack built around GoLogin profiles, proxies, fingerprinting, and anti-detect capabilities.
|
|
14
|
+
|
|
15
|
+
## Why Cloud Browser
|
|
16
|
+
|
|
17
|
+
Local-browser automation is convenient, but it comes with hard limits for agent workflows that need to survive real-world websites:
|
|
18
|
+
|
|
19
|
+
- local browsers are easier to detect
|
|
20
|
+
- local runs usually do not carry profile-based fingerprinting
|
|
21
|
+
- local runs do not come with persistent cloud browser profiles
|
|
22
|
+
- local networking is limited unless you bolt on your own proxy layer
|
|
23
|
+
- local sessions are harder to standardize across agents and environments
|
|
24
|
+
|
|
25
|
+
GoLogin Agent CLI takes the opposite approach:
|
|
26
|
+
|
|
27
|
+
- cloud browser runtime instead of a local browser process
|
|
28
|
+
- GoLogin profiles as the session identity layer
|
|
29
|
+
- proxy-aware browser sessions
|
|
30
|
+
- fingerprint and anti-detect capabilities inherited from GoLogin
|
|
31
|
+
- a persistent daemon that keeps agent sessions alive across CLI calls
|
|
32
|
+
|
|
33
|
+
## Architecture
|
|
34
|
+
|
|
35
|
+
The system has two parts:
|
|
36
|
+
|
|
37
|
+
- `gologin-agent-browser` CLI
|
|
38
|
+
- a persistent local daemon
|
|
39
|
+
|
|
40
|
+
The CLI parses commands, auto-starts the daemon when needed, and prints compact output for agents. The daemon owns live browser sessions, connects to GoLogin Cloud Browser through Playwright `connectOverCDP`, keeps the active page in memory, builds snapshots, resolves refs like `@e1`, and tracks session metadata such as proxy mode, idle timeout, and generated artifacts.
|
|
41
|
+
|
|
42
|
+
If you do not provide a profile id, the daemon creates a temporary GoLogin cloud profile through the GoLogin API, uses it to open the session, and attempts to delete it when the session is closed.
|
|
43
|
+
|
|
44
|
+
Transport is local only:
|
|
45
|
+
|
|
46
|
+
- Unix socket at `${TMPDIR:-/tmp}/gologin-agent-browser.sock` on Unix-like systems
|
|
47
|
+
- localhost HTTP on `127.0.0.1:${GOLOGIN_DAEMON_PORT:-44777}`
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
Node.js 18+ is required.
|
|
52
|
+
|
|
53
|
+
Install from npm:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install -g gologin-agent-browser-cli
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Run it directly:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
gologin-agent-browser --help
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Or use it without a global install:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
npx gologin-agent-browser-cli --help
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Developer setup from source:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
git clone https://gitlab.com/easync/agent-browser.git
|
|
75
|
+
cd agent-browser
|
|
76
|
+
npm install
|
|
77
|
+
npm run build
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Required Environment
|
|
81
|
+
|
|
82
|
+
- `GOLOGIN_TOKEN` required for `open`
|
|
83
|
+
- `GOLOGIN_PROFILE_ID` optional default profile for `open`
|
|
84
|
+
- `GOLOGIN_DAEMON_PORT` optional, defaults to `44777`
|
|
85
|
+
- `GOLOGIN_CONNECT_BASE` optional, defaults to `https://cloudbrowser.gologin.com/connect`
|
|
86
|
+
|
|
87
|
+
Optional config file:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"token": "from-your-own-secret-store",
|
|
92
|
+
"defaultProfileId": "profile-id",
|
|
93
|
+
"daemonPort": 44777,
|
|
94
|
+
"connectBase": "https://cloudbrowser.gologin.com/connect"
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Save it as `~/.gologin-agent-browser/config.json`.
|
|
99
|
+
|
|
100
|
+
## Quickstart
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
export GOLOGIN_TOKEN=your_token
|
|
104
|
+
|
|
105
|
+
gologin-agent-browser open https://example.com
|
|
106
|
+
gologin-agent-browser snapshot -i
|
|
107
|
+
gologin-agent-browser current
|
|
108
|
+
gologin-agent-browser click @e3
|
|
109
|
+
gologin-agent-browser fill "input[name='email']" "test@example.com"
|
|
110
|
+
gologin-agent-browser scroll down 600
|
|
111
|
+
gologin-agent-browser get title
|
|
112
|
+
gologin-agent-browser pdf page.pdf
|
|
113
|
+
gologin-agent-browser screenshot page.png --annotate
|
|
114
|
+
gologin-agent-browser sessions
|
|
115
|
+
gologin-agent-browser close
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
More examples:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
gologin-agent-browser open https://example.com --proxy-host 1.2.3.4 --proxy-port 8080 --proxy-mode http --idle-timeout-ms 300000
|
|
122
|
+
gologin-agent-browser open https://example.com --profile your-preconfigured-gologin-profile
|
|
123
|
+
gologin-agent-browser click "a[href*='iana']"
|
|
124
|
+
gologin-agent-browser type @e4 "hello world"
|
|
125
|
+
gologin-agent-browser focus "input[name='email']"
|
|
126
|
+
gologin-agent-browser press Enter
|
|
127
|
+
gologin-agent-browser select "select[name='plan']" pro
|
|
128
|
+
gologin-agent-browser check "input[name='terms']"
|
|
129
|
+
gologin-agent-browser uncheck "input[name='newsletter']"
|
|
130
|
+
gologin-agent-browser scrollintoview "#submit"
|
|
131
|
+
gologin-agent-browser find label "Email" fill "test@example.com"
|
|
132
|
+
gologin-agent-browser upload "input[type='file']" /absolute/path/to/avatar.png
|
|
133
|
+
gologin-agent-browser wait --text "Welcome"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Commands
|
|
137
|
+
|
|
138
|
+
- `open <url> [--profile <profileId>] [--session <sessionId>] [--idle-timeout-ms <ms>]`
|
|
139
|
+
- `open <url> [--proxy-host <host> --proxy-port <port> --proxy-mode <http|socks4|socks5> --proxy-user <user> --proxy-pass <pass>]`
|
|
140
|
+
- `snapshot [--session <sessionId>] [--interactive]`
|
|
141
|
+
- `click <target> [--session <sessionId>]`
|
|
142
|
+
- `dblclick <target> [--session <sessionId>]`
|
|
143
|
+
- `focus <target> [--session <sessionId>]`
|
|
144
|
+
- `type <target> <text> [--session <sessionId>]`
|
|
145
|
+
- `fill <target> <text> [--session <sessionId>]`
|
|
146
|
+
- `hover <target> [--session <sessionId>]`
|
|
147
|
+
- `select <target> <value> [--session <sessionId>]`
|
|
148
|
+
- `check <target> [--session <sessionId>]`
|
|
149
|
+
- `uncheck <target> [--session <sessionId>]`
|
|
150
|
+
- `press <key> [target] [--session <sessionId>]`
|
|
151
|
+
- `scroll <up|down|left|right> [pixels] [--target <target>] [--session <sessionId>]`
|
|
152
|
+
- `scrollintoview <target> [--session <sessionId>]`
|
|
153
|
+
- `wait <target|ms> [--text <text>] [--url <pattern>] [--load <state>] [--session <sessionId>]`
|
|
154
|
+
- `get <text|value|html|title|url> [target] [--session <sessionId>]`
|
|
155
|
+
- `find <role|text|label|placeholder|first|last|nth> ...`
|
|
156
|
+
- `upload <target> <file...> [--session <sessionId>]`
|
|
157
|
+
- `pdf <path> [--session <sessionId>]`
|
|
158
|
+
- `screenshot <path> [--annotate] [--session <sessionId>]`
|
|
159
|
+
- `close [--session <sessionId>]`
|
|
160
|
+
- `sessions`
|
|
161
|
+
- `current`
|
|
162
|
+
|
|
163
|
+
## Example Session Flow
|
|
164
|
+
|
|
165
|
+
`snapshot` produces compact output designed for agent consumption:
|
|
166
|
+
|
|
167
|
+
```text
|
|
168
|
+
session=s1 url=https://example.com/
|
|
169
|
+
- heading "Example Domain" [ref=@e1]
|
|
170
|
+
- paragraph "This domain is for use in illustrative examples in documents." [ref=@e2]
|
|
171
|
+
- link "More information..." [ref=@e3]
|
|
172
|
+
- checkbox "Accept terms" [checked] [ref=@e4]
|
|
173
|
+
- select "Plan" [selected=Pro] [ref=@e5]
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
Agents can then use those refs:
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
gologin-agent-browser click @e3
|
|
180
|
+
gologin-agent-browser type @e4 "hello world"
|
|
181
|
+
gologin-agent-browser fill "input[name='email']" "test@example.com"
|
|
182
|
+
gologin-agent-browser find role button click --name "Submit"
|
|
183
|
+
gologin-agent-browser screenshot page.png --annotate
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Targets can be either snapshot refs like `@e4` or raw Playwright/CSS selectors. `find` adds semantic locator flows similar to agent-browser.
|
|
187
|
+
|
|
188
|
+
`open`, `current`, and `sessions` also expose session metadata in a shell-friendly form:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
session=s1 url=https://example.com snapshot=fresh proxy=http:1.2.3.4:8080 idleTimeoutMs=300000 shot=/tmp/page.png pdf=/tmp/page.pdf
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
When screenshots or PDFs are generated, `current` and `sessions` include the latest artifact paths as `shot=...` and `pdf=...`.
|
|
195
|
+
|
|
196
|
+
Supported aliases:
|
|
197
|
+
|
|
198
|
+
- `goto`, `navigate` -> `open`
|
|
199
|
+
- `key` -> `press`
|
|
200
|
+
- `scrollinto` -> `scrollintoview`
|
|
201
|
+
- `quit`, `exit` -> `close`
|
|
202
|
+
|
|
203
|
+
## Current Scope
|
|
204
|
+
|
|
205
|
+
- Session persistence lasts as long as the local daemon is running. Restarting the daemon clears in-memory sessions and refs.
|
|
206
|
+
- Idle timeout is a local daemon policy. It does not change GoLogin account-level cloud limits.
|
|
207
|
+
- Snapshot and ref resolution are best-effort. Dynamic pages can invalidate refs after heavy DOM changes or navigation.
|
|
208
|
+
- Snapshot output is compact and accessibility-informed, but it is not a full accessibility tree dump.
|
|
209
|
+
- Annotated screenshots are based on the current snapshot/ref model, so labels are also best-effort on highly dynamic pages.
|
|
210
|
+
- The daemon keeps only the latest snapshot ref map for each session.
|
|
211
|
+
- Real browser sessions require a valid GoLogin Cloud Browser account and token. A profile id is optional.
|
|
212
|
+
- Token-only mode works by provisioning a temporary cloud profile through the GoLogin API before connecting to Cloud Browser.
|
|
213
|
+
- Proxy support is cloud-profile based. Temporary profiles can be created with a custom proxy definition, and existing GoLogin profiles can be reused with `--profile` if they already have a managed proxy attached.
|
|
214
|
+
- Local Orbita is intentionally out of scope. This project targets GoLogin Cloud Browser only.
|
|
215
|
+
- GoLogin cloud live-view URLs are not auto-fetched by default because the current endpoint can interfere with an active CDP session.
|
|
216
|
+
- Playwright is the automation layer on top of GoLogin Cloud Browser. The browser runtime itself does not expose built-in agent actions such as `click()` or `type()`.
|
|
217
|
+
|
|
218
|
+
## Live Smoke Check
|
|
219
|
+
|
|
220
|
+
The project includes a smoke test path that only runs when `GOLOGIN_TOKEN` is present in the environment. If `GOLOGIN_PROFILE_ID` is also set, the smoke flow can reuse that profile; otherwise GoLogin can create a temporary session profile automatically. Secrets are never written into source files, tests, or examples.
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const node_http_1 = __importDefault(require("node:http"));
|
|
8
|
+
const node_child_process_1 = require("node:child_process");
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
const check_1 = require("./commands/check");
|
|
12
|
+
const click_1 = require("./commands/click");
|
|
13
|
+
const close_1 = require("./commands/close");
|
|
14
|
+
const current_1 = require("./commands/current");
|
|
15
|
+
const dblclick_1 = require("./commands/dblclick");
|
|
16
|
+
const fill_1 = require("./commands/fill");
|
|
17
|
+
const find_1 = require("./commands/find");
|
|
18
|
+
const focus_1 = require("./commands/focus");
|
|
19
|
+
const get_1 = require("./commands/get");
|
|
20
|
+
const hover_1 = require("./commands/hover");
|
|
21
|
+
const open_1 = require("./commands/open");
|
|
22
|
+
const pdf_1 = require("./commands/pdf");
|
|
23
|
+
const press_1 = require("./commands/press");
|
|
24
|
+
const screenshot_1 = require("./commands/screenshot");
|
|
25
|
+
const scroll_1 = require("./commands/scroll");
|
|
26
|
+
const scrollIntoView_1 = require("./commands/scrollIntoView");
|
|
27
|
+
const sessions_1 = require("./commands/sessions");
|
|
28
|
+
const snapshot_1 = require("./commands/snapshot");
|
|
29
|
+
const select_1 = require("./commands/select");
|
|
30
|
+
const type_1 = require("./commands/type");
|
|
31
|
+
const uncheck_1 = require("./commands/uncheck");
|
|
32
|
+
const upload_1 = require("./commands/upload");
|
|
33
|
+
const wait_1 = require("./commands/wait");
|
|
34
|
+
const config_1 = require("./lib/config");
|
|
35
|
+
const errors_1 = require("./lib/errors");
|
|
36
|
+
const utils_1 = require("./lib/utils");
|
|
37
|
+
function printUsage() {
|
|
38
|
+
process.stderr.write([
|
|
39
|
+
"GoLogin Agent CLI",
|
|
40
|
+
"",
|
|
41
|
+
"Usage:",
|
|
42
|
+
" gologin-agent-browser <command> [args] [options]",
|
|
43
|
+
"",
|
|
44
|
+
"Commands:",
|
|
45
|
+
" open <url> [--profile <profileId>] [--session <sessionId>] [--idle-timeout-ms <ms>] [--proxy-host <host> --proxy-port <port> --proxy-mode <http|socks4|socks5>] (aliases: goto, navigate)",
|
|
46
|
+
" snapshot [--session <sessionId>] [--interactive|-i]",
|
|
47
|
+
" click <target> [--session <sessionId>]",
|
|
48
|
+
" dblclick <target> [--session <sessionId>]",
|
|
49
|
+
" focus <target> [--session <sessionId>]",
|
|
50
|
+
" type <target> <text> [--session <sessionId>]",
|
|
51
|
+
" fill <target> <text> [--session <sessionId>]",
|
|
52
|
+
" hover <target> [--session <sessionId>]",
|
|
53
|
+
" select <target> <value> [--session <sessionId>]",
|
|
54
|
+
" check <target> [--session <sessionId>]",
|
|
55
|
+
" uncheck <target> [--session <sessionId>]",
|
|
56
|
+
" press <key> [target] [--session <sessionId>] (alias: key)",
|
|
57
|
+
" scroll <up|down|left|right> [pixels] [--target <target>] [--session <sessionId>]",
|
|
58
|
+
" scrollintoview <target> [--session <sessionId>] (alias: scrollinto)",
|
|
59
|
+
" wait <target|ms> [--text <text>] [--url <pattern>] [--load <state>] [--session <sessionId>]",
|
|
60
|
+
" get <text|value|html|title|url> [target] [--session <sessionId>]",
|
|
61
|
+
" find <role|text|label|placeholder|first|last|nth> ... [--exact]",
|
|
62
|
+
" upload <target> <file...> [--session <sessionId>]",
|
|
63
|
+
" pdf <path> [--session <sessionId>]",
|
|
64
|
+
" screenshot <path> [--annotate] [--session <sessionId>]",
|
|
65
|
+
" close [--session <sessionId>] (aliases: quit, exit)",
|
|
66
|
+
" sessions",
|
|
67
|
+
" current",
|
|
68
|
+
"",
|
|
69
|
+
"Environment:",
|
|
70
|
+
" GOLOGIN_TOKEN",
|
|
71
|
+
" GOLOGIN_PROFILE_ID",
|
|
72
|
+
" GOLOGIN_DAEMON_PORT",
|
|
73
|
+
" GOLOGIN_CONNECT_BASE"
|
|
74
|
+
].join("\n") + "\n");
|
|
75
|
+
}
|
|
76
|
+
function projectRootFromCli() {
|
|
77
|
+
return node_path_1.default.resolve(__dirname, "..");
|
|
78
|
+
}
|
|
79
|
+
function buildDaemonSpawnCommand(projectRoot) {
|
|
80
|
+
const distServerPath = node_path_1.default.join(projectRoot, "dist", "daemon", "server.js");
|
|
81
|
+
if (node_fs_1.default.existsSync(distServerPath)) {
|
|
82
|
+
return {
|
|
83
|
+
command: process.execPath,
|
|
84
|
+
args: [distServerPath]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
const tsxCli = node_path_1.default.join(projectRoot, "node_modules", "tsx", "dist", "cli.mjs");
|
|
88
|
+
const srcServerPath = node_path_1.default.join(projectRoot, "src", "daemon", "server.ts");
|
|
89
|
+
if (node_fs_1.default.existsSync(tsxCli) && node_fs_1.default.existsSync(srcServerPath)) {
|
|
90
|
+
return {
|
|
91
|
+
command: process.execPath,
|
|
92
|
+
args: [tsxCli, srcServerPath]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
throw new errors_1.AppError("DAEMON_UNREACHABLE", "Daemon entrypoint is missing. Run npm install and npm run build first.", 500);
|
|
96
|
+
}
|
|
97
|
+
function requestOverHttp(transport, method, requestPath, body) {
|
|
98
|
+
return new Promise((resolve, reject) => {
|
|
99
|
+
const payload = body === undefined ? undefined : Buffer.from(JSON.stringify(body));
|
|
100
|
+
const options = transport.kind === "socket"
|
|
101
|
+
? {
|
|
102
|
+
socketPath: transport.socketPath,
|
|
103
|
+
path: requestPath,
|
|
104
|
+
method,
|
|
105
|
+
headers: payload
|
|
106
|
+
? {
|
|
107
|
+
"content-type": "application/json",
|
|
108
|
+
"content-length": String(payload.length)
|
|
109
|
+
}
|
|
110
|
+
: undefined
|
|
111
|
+
}
|
|
112
|
+
: {
|
|
113
|
+
host: transport.host,
|
|
114
|
+
port: transport.port,
|
|
115
|
+
path: requestPath,
|
|
116
|
+
method,
|
|
117
|
+
headers: payload
|
|
118
|
+
? {
|
|
119
|
+
"content-type": "application/json",
|
|
120
|
+
"content-length": String(payload.length)
|
|
121
|
+
}
|
|
122
|
+
: undefined
|
|
123
|
+
};
|
|
124
|
+
const request = node_http_1.default.request(options, (response) => {
|
|
125
|
+
const chunks = [];
|
|
126
|
+
response.on("data", (chunk) => chunks.push(chunk));
|
|
127
|
+
response.on("end", () => {
|
|
128
|
+
const raw = chunks.length > 0 ? Buffer.concat(chunks).toString("utf8") : "{}";
|
|
129
|
+
const parsed = raw.length > 0 ? JSON.parse(raw) : undefined;
|
|
130
|
+
if ((response.statusCode ?? 500) >= 400) {
|
|
131
|
+
if ((0, utils_1.isDaemonErrorResponse)(parsed)) {
|
|
132
|
+
reject((0, errors_1.fromDaemonError)(parsed));
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
reject(new errors_1.AppError("INTERNAL_ERROR", `Daemon request failed with status ${response.statusCode}`, 500));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
resolve(parsed);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
request.on("error", (error) => reject(error));
|
|
142
|
+
if (payload) {
|
|
143
|
+
request.write(payload);
|
|
144
|
+
}
|
|
145
|
+
request.end();
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
async function probeTransport(transport) {
|
|
149
|
+
try {
|
|
150
|
+
await requestOverHttp(transport, "GET", "/health");
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
catch {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async function waitForDaemon(transports, timeoutMs) {
|
|
158
|
+
const start = Date.now();
|
|
159
|
+
while (Date.now() - start < timeoutMs) {
|
|
160
|
+
for (const transport of transports) {
|
|
161
|
+
if (await probeTransport(transport)) {
|
|
162
|
+
return transport;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
166
|
+
}
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
async function ensureDaemon(config) {
|
|
170
|
+
const transports = [];
|
|
171
|
+
if (process.platform !== "win32") {
|
|
172
|
+
transports.push({
|
|
173
|
+
kind: "socket",
|
|
174
|
+
socketPath: config.socketPath
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
transports.push({
|
|
178
|
+
kind: "http",
|
|
179
|
+
host: config.daemonHost,
|
|
180
|
+
port: config.daemonPort
|
|
181
|
+
});
|
|
182
|
+
for (const transport of transports) {
|
|
183
|
+
if (await probeTransport(transport)) {
|
|
184
|
+
return transport;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const projectRoot = projectRootFromCli();
|
|
188
|
+
const command = buildDaemonSpawnCommand(projectRoot);
|
|
189
|
+
const child = (0, node_child_process_1.spawn)(command.command, command.args, {
|
|
190
|
+
cwd: projectRoot,
|
|
191
|
+
detached: true,
|
|
192
|
+
stdio: "ignore",
|
|
193
|
+
env: process.env
|
|
194
|
+
});
|
|
195
|
+
child.unref();
|
|
196
|
+
const resolved = await waitForDaemon(transports, 5_000);
|
|
197
|
+
if (!resolved) {
|
|
198
|
+
throw new errors_1.AppError("DAEMON_UNREACHABLE", "Local daemon did not start in time", 503);
|
|
199
|
+
}
|
|
200
|
+
return resolved;
|
|
201
|
+
}
|
|
202
|
+
function createDaemonClient(transport) {
|
|
203
|
+
return {
|
|
204
|
+
transport,
|
|
205
|
+
async request(method, requestPath, body) {
|
|
206
|
+
return (await requestOverHttp(transport, method, requestPath, body));
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
async function runCommand(command, context, args) {
|
|
211
|
+
switch (command) {
|
|
212
|
+
case "open":
|
|
213
|
+
await (0, open_1.runOpenCommand)(context, args);
|
|
214
|
+
return;
|
|
215
|
+
case "snapshot":
|
|
216
|
+
await (0, snapshot_1.runSnapshotCommand)(context, args);
|
|
217
|
+
return;
|
|
218
|
+
case "click":
|
|
219
|
+
await (0, click_1.runClickCommand)(context, args);
|
|
220
|
+
return;
|
|
221
|
+
case "dblclick":
|
|
222
|
+
await (0, dblclick_1.runDoubleClickCommand)(context, args);
|
|
223
|
+
return;
|
|
224
|
+
case "focus":
|
|
225
|
+
await (0, focus_1.runFocusCommand)(context, args);
|
|
226
|
+
return;
|
|
227
|
+
case "type":
|
|
228
|
+
await (0, type_1.runTypeCommand)(context, args);
|
|
229
|
+
return;
|
|
230
|
+
case "fill":
|
|
231
|
+
await (0, fill_1.runFillCommand)(context, args);
|
|
232
|
+
return;
|
|
233
|
+
case "hover":
|
|
234
|
+
await (0, hover_1.runHoverCommand)(context, args);
|
|
235
|
+
return;
|
|
236
|
+
case "select":
|
|
237
|
+
await (0, select_1.runSelectCommand)(context, args);
|
|
238
|
+
return;
|
|
239
|
+
case "check":
|
|
240
|
+
await (0, check_1.runCheckCommand)(context, args);
|
|
241
|
+
return;
|
|
242
|
+
case "uncheck":
|
|
243
|
+
await (0, uncheck_1.runUncheckCommand)(context, args);
|
|
244
|
+
return;
|
|
245
|
+
case "press":
|
|
246
|
+
await (0, press_1.runPressCommand)(context, args);
|
|
247
|
+
return;
|
|
248
|
+
case "scroll":
|
|
249
|
+
await (0, scroll_1.runScrollCommand)(context, args);
|
|
250
|
+
return;
|
|
251
|
+
case "scrollintoview":
|
|
252
|
+
await (0, scrollIntoView_1.runScrollIntoViewCommand)(context, args);
|
|
253
|
+
return;
|
|
254
|
+
case "wait":
|
|
255
|
+
await (0, wait_1.runWaitCommand)(context, args);
|
|
256
|
+
return;
|
|
257
|
+
case "get":
|
|
258
|
+
await (0, get_1.runGetCommand)(context, args);
|
|
259
|
+
return;
|
|
260
|
+
case "find":
|
|
261
|
+
await (0, find_1.runFindCommand)(context, args);
|
|
262
|
+
return;
|
|
263
|
+
case "upload":
|
|
264
|
+
await (0, upload_1.runUploadCommand)(context, args);
|
|
265
|
+
return;
|
|
266
|
+
case "pdf":
|
|
267
|
+
await (0, pdf_1.runPdfCommand)(context, args);
|
|
268
|
+
return;
|
|
269
|
+
case "screenshot":
|
|
270
|
+
await (0, screenshot_1.runScreenshotCommand)(context, args);
|
|
271
|
+
return;
|
|
272
|
+
case "close":
|
|
273
|
+
await (0, close_1.runCloseCommand)(context, args);
|
|
274
|
+
return;
|
|
275
|
+
case "sessions":
|
|
276
|
+
await (0, sessions_1.runSessionsCommand)(context, args);
|
|
277
|
+
return;
|
|
278
|
+
case "current":
|
|
279
|
+
await (0, current_1.runCurrentCommand)(context, args);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
function normalizeCommand(commandArg) {
|
|
284
|
+
const aliases = {
|
|
285
|
+
goto: "open",
|
|
286
|
+
navigate: "open",
|
|
287
|
+
dblclick: "dblclick",
|
|
288
|
+
key: "press",
|
|
289
|
+
scrollinto: "scrollintoview",
|
|
290
|
+
quit: "close",
|
|
291
|
+
exit: "close"
|
|
292
|
+
};
|
|
293
|
+
if (aliases[commandArg]) {
|
|
294
|
+
return aliases[commandArg];
|
|
295
|
+
}
|
|
296
|
+
const directCommands = new Set([
|
|
297
|
+
"open",
|
|
298
|
+
"snapshot",
|
|
299
|
+
"click",
|
|
300
|
+
"dblclick",
|
|
301
|
+
"focus",
|
|
302
|
+
"type",
|
|
303
|
+
"fill",
|
|
304
|
+
"hover",
|
|
305
|
+
"select",
|
|
306
|
+
"check",
|
|
307
|
+
"uncheck",
|
|
308
|
+
"press",
|
|
309
|
+
"scroll",
|
|
310
|
+
"scrollintoview",
|
|
311
|
+
"wait",
|
|
312
|
+
"get",
|
|
313
|
+
"find",
|
|
314
|
+
"upload",
|
|
315
|
+
"pdf",
|
|
316
|
+
"screenshot",
|
|
317
|
+
"close",
|
|
318
|
+
"sessions",
|
|
319
|
+
"current"
|
|
320
|
+
]);
|
|
321
|
+
return directCommands.has(commandArg) ? commandArg : undefined;
|
|
322
|
+
}
|
|
323
|
+
async function main() {
|
|
324
|
+
const argv = process.argv.slice(2);
|
|
325
|
+
const commandArg = argv[0];
|
|
326
|
+
if (!commandArg || commandArg === "--help" || commandArg === "-h") {
|
|
327
|
+
printUsage();
|
|
328
|
+
process.exit(commandArg ? 0 : 1);
|
|
329
|
+
}
|
|
330
|
+
const command = normalizeCommand(commandArg);
|
|
331
|
+
if (!command) {
|
|
332
|
+
throw new errors_1.AppError("BAD_REQUEST", `Unknown command: ${commandArg}`, 400);
|
|
333
|
+
}
|
|
334
|
+
const config = (0, config_1.loadConfig)();
|
|
335
|
+
const transport = await ensureDaemon(config);
|
|
336
|
+
const client = createDaemonClient(transport);
|
|
337
|
+
const health = await client.request("GET", "/health");
|
|
338
|
+
if (!health.ok) {
|
|
339
|
+
throw new errors_1.AppError("DAEMON_UNREACHABLE", "Daemon health probe failed", 503);
|
|
340
|
+
}
|
|
341
|
+
const context = {
|
|
342
|
+
client,
|
|
343
|
+
stdout: process.stdout,
|
|
344
|
+
stderr: process.stderr,
|
|
345
|
+
cwd: process.cwd()
|
|
346
|
+
};
|
|
347
|
+
await runCommand(command, context, argv.slice(1));
|
|
348
|
+
}
|
|
349
|
+
main().catch((error) => {
|
|
350
|
+
process.stderr.write(`${(0, errors_1.formatErrorLine)(error)}\n`);
|
|
351
|
+
process.exit(error instanceof errors_1.AppError ? 1 : 1);
|
|
352
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runCheckCommand = runCheckCommand;
|
|
4
|
+
const errors_1 = require("../lib/errors");
|
|
5
|
+
const utils_1 = require("../lib/utils");
|
|
6
|
+
const shared_1 = require("./shared");
|
|
7
|
+
async function runCheckCommand(context, argv) {
|
|
8
|
+
const parsed = (0, utils_1.parseArgs)(argv);
|
|
9
|
+
const target = parsed.positional[0];
|
|
10
|
+
const sessionId = (0, utils_1.getFlagString)(parsed, "session");
|
|
11
|
+
if (!target) {
|
|
12
|
+
throw new errors_1.AppError("BAD_REQUEST", "Usage: gologin-agent-browser check <target> [--session <sessionId>]", 400);
|
|
13
|
+
}
|
|
14
|
+
const resolvedSessionId = await (0, shared_1.resolveSessionId)(context, sessionId);
|
|
15
|
+
const response = await context.client.request("POST", `/sessions/${resolvedSessionId}/check`, { target });
|
|
16
|
+
(0, shared_1.writeActionResult)(context, "checked", target, response);
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runClickCommand = runClickCommand;
|
|
4
|
+
const errors_1 = require("../lib/errors");
|
|
5
|
+
const utils_1 = require("../lib/utils");
|
|
6
|
+
const shared_1 = require("./shared");
|
|
7
|
+
async function runClickCommand(context, argv) {
|
|
8
|
+
const parsed = (0, utils_1.parseArgs)(argv);
|
|
9
|
+
const target = parsed.positional[0];
|
|
10
|
+
const sessionId = (0, utils_1.getFlagString)(parsed, "session");
|
|
11
|
+
if (!target) {
|
|
12
|
+
throw new errors_1.AppError("BAD_REQUEST", "Usage: gologin-agent-browser click <target> [--session <sessionId>]", 400);
|
|
13
|
+
}
|
|
14
|
+
const resolvedSessionId = await (0, shared_1.resolveSessionId)(context, sessionId);
|
|
15
|
+
const response = await context.client.request("POST", `/sessions/${resolvedSessionId}/click`, { target });
|
|
16
|
+
(0, shared_1.writeActionResult)(context, "clicked", target, response);
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runCloseCommand = runCloseCommand;
|
|
4
|
+
const utils_1 = require("../lib/utils");
|
|
5
|
+
async function runCloseCommand(context, argv) {
|
|
6
|
+
const parsed = (0, utils_1.parseArgs)(argv);
|
|
7
|
+
const sessionId = (0, utils_1.getFlagString)(parsed, "session");
|
|
8
|
+
const resolvedSessionId = sessionId ??
|
|
9
|
+
(await context.client.request("GET", "/sessions/current")).sessionId;
|
|
10
|
+
const response = await context.client.request("POST", `/sessions/${resolvedSessionId}/close`);
|
|
11
|
+
context.stdout.write(`closed session=${response.sessionId}\n`);
|
|
12
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runCurrentCommand = runCurrentCommand;
|
|
4
|
+
const utils_1 = require("../lib/utils");
|
|
5
|
+
async function runCurrentCommand(context, argv) {
|
|
6
|
+
(0, utils_1.parseArgs)(argv);
|
|
7
|
+
const response = await context.client.request("GET", "/sessions/current");
|
|
8
|
+
context.stdout.write(`${(0, utils_1.formatCurrentLine)(response)}\n`);
|
|
9
|
+
}
|