semrush-cli 0.4.0 → 0.5.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 +169 -165
- package/assets/demo-keywords.gif +0 -0
- package/assets/demo-keywords.png +0 -0
- package/assets/demo-projects.gif +0 -0
- package/assets/demo-projects.png +0 -0
- package/assets/logo-lockup.svg +10 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +4 -8
- package/dist/{auth-CHCXZZpR.js → auth-CrYOA8QT.js} +4 -4
- package/dist/{auth-CHCXZZpR.js.map → auth-CrYOA8QT.js.map} +1 -1
- package/dist/{browser-layer-DgW0vW92.js → browser-layer-gdm03TCV.js} +6 -6
- package/dist/browser-layer-gdm03TCV.js.map +1 -0
- package/dist/cli.js +18 -18
- package/dist/cli.js.map +1 -1
- package/dist/{errors-BjuHkMt_.js → errors-DNn_XeoU.js} +2 -2
- package/dist/errors-DNn_XeoU.js.map +1 -0
- package/dist/{mcp-BPznArmB.js → mcp-DqvGEnxx.js} +6 -6
- package/dist/{mcp-BPznArmB.js.map → mcp-DqvGEnxx.js.map} +1 -1
- package/dist/{session-layer-DJzN9Xx2.js → session-layer-Bq5OpnR5.js} +5 -5
- package/dist/{session-layer-DJzN9Xx2.js.map → session-layer-Bq5OpnR5.js.map} +1 -1
- package/dist/{validation-CenS7xI_.js → validation-B3hdOxdH.js} +4 -4
- package/dist/validation-B3hdOxdH.js.map +1 -0
- package/dist/validation-B8xjCr9w.js +4 -0
- package/package.json +2 -2
- package/dist/browser-layer-DgW0vW92.js.map +0 -1
- package/dist/errors-BjuHkMt_.js.map +0 -1
- package/dist/validation-B5M_ocvQ.js +0 -4
- package/dist/validation-CenS7xI_.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,233 +1,237 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<img src="assets/logo.svg" width="
|
|
2
|
+
<img src="assets/logo-lockup.svg" width="720" alt="semrush cli logo">
|
|
3
3
|
</p>
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
<p align="center">
|
|
6
|
-
<a href="https://github.com/Microck/semrush-cli/
|
|
7
|
-
<a href="https://www.npmjs.com/package/semrush-cli"><img src="https://img.shields.io/npm/
|
|
8
|
-
<a href="https://github.com/Microck/semrush-cli/
|
|
9
|
-
<a href="LICENSE"><img src="https://img.shields.io/
|
|
6
|
+
<a href="https://github.com/Microck/semrush-cli/releases"><img src="https://img.shields.io/github/v/release/Microck/semrush-cli?display_name=tag&style=flat-square&label=release&color=000000" alt="release badge"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/semrush-cli"><img src="https://img.shields.io/npm/dt/semrush-cli?style=flat-square&label=downloads&color=000000" alt="npm downloads"></a>
|
|
8
|
+
<a href="https://github.com/Microck/semrush-cli/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/Microck/semrush-cli/ci.yml?branch=main&style=flat-square&label=ci&color=000000" alt="ci badge"></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-mit-000000?style=flat-square" alt="license badge"></a>
|
|
10
10
|
</p>
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
-
semrush
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
`semrush` is a terminal cli for semrush. research keywords, check rankings and backlinks, compare competitors, manage projects and keyword lists, and run site audits from your own account. output is json, so you can pipe it into `jq` or give the same commands to an agent through mcp. built with typescript and effect.
|
|
15
|
+
|
|
16
|
+
[documentation](https://semrush.micr.dev) | [command reference](https://semrush.micr.dev/docs/reports) | [npm](https://www.npmjs.com/package/semrush-cli) | [mcp](https://semrush.micr.dev/docs/mcp)
|
|
17
17
|
|
|
18
|
-
[
|
|
18
|
+

|
|
19
19
|
|
|
20
20
|
## why
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
scripts, account totals and pagination, project management, and encrypted session storage.
|
|
24
|
-
no Semrush developer API key or Python runtime required.
|
|
22
|
+
if you already pay for semrush, you should be able to use it from the terminal too. this cli uses your existing web session, so there is no developer api key to buy or configure. look up a keyword, save the result, compare a few domains, or let an agent do it through mcp.
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
your plan's permissions and limits still apply. it doesn't cover the whole semrush suite. the [coverage page](https://semrush.micr.dev/docs/coverage) lists what works and what still needs the web ui.
|
|
27
25
|
|
|
28
26
|
## quickstart
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
### install
|
|
29
|
+
|
|
30
|
+
requires node.js 22 or newer. linux, macos and windows are supported.
|
|
32
31
|
|
|
33
|
-
```
|
|
32
|
+
```bash
|
|
34
33
|
npm install -g semrush-cli
|
|
35
|
-
semrush
|
|
36
|
-
semrush
|
|
37
|
-
semrush
|
|
34
|
+
semrush setup
|
|
35
|
+
semrush auth login
|
|
36
|
+
semrush keyword-overview --keyword 'email marketing'
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`semrush-cli` is the npm package. `semrush` is the command.
|
|
40
|
+
|
|
41
|
+
`setup` downloads chromium and starts the bundled galleton session engine. there is no separate python or go install. on linux, if chromium needs system libraries, run `npx patchright install-deps chromium`.
|
|
42
|
+
|
|
43
|
+
### auth
|
|
44
|
+
|
|
45
|
+
`semrush auth login` opens a browser. sign in to semrush there and the cli saves the session in galleton's encrypted vault. it doesn't ask for your password in the terminal.
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
semrush auth login
|
|
49
|
+
semrush auth status --check --json
|
|
50
|
+
semrush doctor
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
already have a signed-in browser with a local debugging port? import its session instead:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
semrush auth import --cdp-url http://127.0.0.1:9234
|
|
57
|
+
semrush doctor
|
|
38
58
|
```
|
|
39
59
|
|
|
40
|
-
|
|
41
|
-
opens an isolated browser for sign-in, verifies the session, and stores it in
|
|
42
|
-
Galleton. It requires a graphical desktop. On Linux, missing Chromium system
|
|
43
|
-
libraries can be installed with `npx patchright install-deps chromium`.
|
|
60
|
+
the browser has to be running with debugging enabled. import doesn't start or stop it. see the [authentication guide](https://semrush.micr.dev/docs/authentication) for headless hosts and remote browsers.
|
|
44
61
|
|
|
45
|
-
|
|
62
|
+
semrush can expire your session. sign in or import again when that happens. the cli saves rotated cookies after successful requests, but doesn't renew expired sessions in the background.
|
|
46
63
|
|
|
47
|
-
```
|
|
48
|
-
semrush
|
|
49
|
-
semrush
|
|
64
|
+
```bash
|
|
65
|
+
semrush auth logout
|
|
66
|
+
semrush daemon stop
|
|
67
|
+
semrush daemon start
|
|
50
68
|
```
|
|
51
69
|
|
|
52
|
-
|
|
53
|
-
supports the browser managed by [semrush-web-mcp](https://github.com/Microck/semrush-web-mcp).
|
|
54
|
-
Import does not stop or change that browser. Keep CDP private; forward it over
|
|
55
|
-
SSH when necessary.
|
|
70
|
+
logout forgets this cli's saved session. it doesn't log out your other browsers. the daemon keeps running after the terminal closes. start it again after a reboot.
|
|
56
71
|
|
|
57
|
-
##
|
|
72
|
+
## command surface
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
74
|
+
89 operations, available as cli commands and mcp tools.
|
|
75
|
+
|
|
76
|
+
| command | what it does |
|
|
77
|
+
| --- | --- |
|
|
78
|
+
| `keyword-overview`, `keyword-magic` | volume, difficulty, cpc, intent, related keywords and filtered research |
|
|
79
|
+
| `keyword-serp`, `keyword-ads`, `keyword-clusters` | search results, ads and keyword clusters |
|
|
80
|
+
| `domain-overview`, `domain-keywords`, `domain-competitors` | organic rankings and competing domains |
|
|
81
|
+
| `domain-pages`, `domain-subdomains`, `domain-position-changes` | pages, subdomains and ranking changes |
|
|
82
|
+
| `keyword-gap`, `backlink-gap` | compare domains and find keyword or backlink opportunities |
|
|
83
|
+
| `backlink-links`, `backlink-referring-domains`, `backlink-anchors` | inspect backlinks, referring domains and anchor text |
|
|
84
|
+
| `domain-ai-visibility`, `domain-ai-countries`, `domain-ai-sources` | ai visibility, countries and citation sources |
|
|
85
|
+
| `project-create`, `project-get`, `project-update`, `project-delete` | create and manage projects |
|
|
86
|
+
| `project-tags`, `project-set-tags`, `project-pin` | organize your projects |
|
|
87
|
+
| `project-enable-seo`, `project-tools`, `project-toolkits` | enable seo and read project tool status |
|
|
88
|
+
| `keyword-lists`, `keyword-list-create`, `keyword-list-import` | manage keyword lists and import keywords |
|
|
89
|
+
| `keyword-strategy-create`, `keyword-list-cluster` | generate a strategy or cluster a list |
|
|
90
|
+
| `audit-create`, `audit-configure`, `audit-start` | configure and launch a site audit |
|
|
91
|
+
| `audit-status`, `audit-snapshots`, `audit-snapshot` | check progress and read audit results |
|
|
92
|
+
| `reports` | list every operation and its inputs |
|
|
93
|
+
| `mcp` | run the stdio mcp server |
|
|
94
|
+
| `doctor` | check the browser, session engine and semrush connection |
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
semrush reports
|
|
98
|
+
semrush keyword-magic --help
|
|
99
|
+
semrush project-create --help
|
|
68
100
|
```
|
|
69
101
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
automatic Site Audit/Position Tracking setup are separate, explicit commands.
|
|
74
|
-
See [projects](https://semrush.micr.dev/docs/projects) and
|
|
75
|
-
[web UI coverage](https://semrush.micr.dev/docs/coverage).
|
|
102
|
+
global flags: `--json`, `--no-color`, `--no-input`. reports return json with or without `--json`. objects and arrays use json arguments. use `--help` on a command for its filters, pagination and defaults.
|
|
103
|
+
|
|
104
|
+
## examples
|
|
76
105
|
|
|
77
|
-
|
|
106
|
+
### keyword research
|
|
78
107
|
|
|
79
|
-
```
|
|
80
|
-
semrush
|
|
81
|
-
semrush
|
|
82
|
-
semrush
|
|
83
|
-
semrush-cli keyword-list-keywords --basket-id 456 --owner-id 123 --page-size 20 --json
|
|
84
|
-
semrush-cli keyword-list-add-tags --basket-id 456 --owner-id 123 --keywords '["email marketing"]' --tags '["opportunity"]' --confirm
|
|
85
|
-
semrush-cli keyword-strategy-create --name 'example strategy' --domain example.com --dry-run
|
|
108
|
+
```bash
|
|
109
|
+
semrush keyword-overview --keyword 'email marketing'
|
|
110
|
+
semrush keyword-magic --keyword 'email marketing' --questions-only --page-size 20
|
|
111
|
+
semrush keyword-serp --keyword 'email marketing'
|
|
86
112
|
```
|
|
87
113
|
|
|
88
|
-
|
|
89
|
-
and structured strategy generation are separate commands. Metric refresh and
|
|
90
|
-
clustering require explicit confirmation and consume account allowances.
|
|
91
|
-
See [keyword lists](https://semrush.micr.dev/docs/keyword-lists).
|
|
114
|
+
pull out the numbers you care about:
|
|
92
115
|
|
|
93
|
-
|
|
116
|
+
```bash
|
|
117
|
+
semrush keyword-overview --keyword 'email marketing' \
|
|
118
|
+
| jq '.data | {phrase, volume, difficulty, cpc}'
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### competitors and backlinks
|
|
94
122
|
|
|
95
|
-
```
|
|
96
|
-
semrush
|
|
97
|
-
semrush
|
|
98
|
-
semrush
|
|
123
|
+
```bash
|
|
124
|
+
semrush domain-overview --domain openai.com
|
|
125
|
+
semrush domain-keywords --domain openai.com --page-size 20
|
|
126
|
+
semrush keyword-gap --domains '["openai.com","anthropic.com"]'
|
|
127
|
+
semrush backlink-links --domain openai.com
|
|
128
|
+
semrush backlink-gap --domains '["openai.com","anthropic.com"]'
|
|
99
129
|
```
|
|
100
130
|
|
|
101
|
-
|
|
131
|
+
### projects
|
|
132
|
+
|
|
133
|
+
preview a write before sending it:
|
|
102
134
|
|
|
135
|
+
```bash
|
|
136
|
+
semrush project-create --name 'my website' --domain example.com --dry-run
|
|
137
|
+
```
|
|
103
138
|
|
|
104
|
-
|
|
139
|
+
then create it when you're ready:
|
|
105
140
|
|
|
106
|
-
```
|
|
107
|
-
semrush
|
|
108
|
-
semrush-cli keyword-magic --keyword 'email marketing' --questions-only --page-size 20 --json
|
|
109
|
-
semrush-cli domain-keywords --domain openai.com --page 2 --page-size 20 --json
|
|
110
|
-
semrush-cli domain-competitors --domain openai.com --page-size 10 --json
|
|
111
|
-
semrush-cli keyword-gap --domains '["openai.com","anthropic.com"]' --json
|
|
112
|
-
semrush-cli backlink-gap --domains '["openai.com","anthropic.com"]' --json
|
|
113
|
-
semrush-cli backlink-links --domain openai.com --filters '+|type||nofollow' --json
|
|
114
|
-
semrush-cli domain-ai-visibility --domain openai.com --json
|
|
115
|
-
semrush-cli snapshot-dates --database us --json
|
|
116
|
-
semrush-cli reports --json
|
|
117
|
-
semrush-cli domain-keywords --help
|
|
141
|
+
```bash
|
|
142
|
+
semrush project-create --name 'my website' --domain example.com --confirm
|
|
118
143
|
```
|
|
119
144
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
145
|
+

|
|
146
|
+
|
|
147
|
+
writes need `--confirm`. `--dry-run` validates the inputs and prints the request without connecting to semrush. creating a project doesn't start tools or share it.
|
|
148
|
+
|
|
149
|
+
### keyword lists
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
semrush keyword-lists
|
|
153
|
+
semrush keyword-list-create --name 'content ideas' --confirm
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
use the list id and owner id returned by semrush:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
semrush keyword-list-import --basket-id 123 --owner-id 456 \
|
|
160
|
+
--keywords '["email marketing","email automation"]' --database us --confirm
|
|
161
|
+
semrush keyword-list-keywords --basket-id 123 --owner-id 456
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
strategy generation, clustering and metric updates can consume your account allowance. they need explicit confirmation too. see [keyword lists](https://semrush.micr.dev/docs/keyword-lists).
|
|
165
|
+
|
|
166
|
+
### site audits
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
semrush project-enable-seo --folder-id 123 --confirm
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
use the returned seo project `id` as the campaign id:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
semrush audit-create --campaign-id 456 --name 'my audit' \
|
|
176
|
+
--domain example.com --page-limit 100 --confirm
|
|
177
|
+
semrush audit-start --campaign-id 456 --confirm
|
|
178
|
+
semrush audit-status --campaign-id 456
|
|
179
|
+
semrush audit-snapshots --campaign-id 456
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
creating an audit saves its settings. starting it is a separate command. defaults are manual scheduling and email off. see [site audit](https://semrush.micr.dev/docs/site-audit) for crawl settings and limits.
|
|
137
183
|
|
|
138
184
|
## mcp
|
|
139
185
|
|
|
140
|
-
|
|
186
|
+
finish setup and sign in first, then add this to your client's mcp config:
|
|
141
187
|
|
|
142
188
|
```json
|
|
143
189
|
{
|
|
144
190
|
"mcpServers": {
|
|
145
191
|
"semrush": {
|
|
146
|
-
"command": "semrush
|
|
192
|
+
"command": "semrush",
|
|
147
193
|
"args": ["mcp"]
|
|
148
194
|
}
|
|
149
195
|
}
|
|
150
196
|
}
|
|
151
197
|
```
|
|
152
198
|
|
|
153
|
-
|
|
154
|
-
PATH. `npx -y semrush-cli@0.4.0 mcp` also works. MCP tool names use underscores,
|
|
155
|
-
for example `keyword_overview` and `domain_keywords`. Input schemas and reports
|
|
156
|
-
match the CLI. The 67 read tools carry read-only annotations. The 22 write tools require
|
|
157
|
-
`confirm:true`, or accept `dry_run:true` to preview the request. Delete and leave
|
|
158
|
-
also carry destructive annotations. Login, arbitrary URLs and JavaScript
|
|
159
|
-
execution are not MCP tools.
|
|
199
|
+
the server exposes the same operations as the cli. tool names use underscores, such as `keyword_overview` and `project_create`. writes need `confirm: true`, or `dry_run: true` to preview the request. it doesn't expose login, arbitrary urls or javascript execution.
|
|
160
200
|
|
|
161
|
-
##
|
|
201
|
+
## what it looks like
|
|
162
202
|
|
|
163
|
-
|
|
164
|
-
semrush-cli auth status --check --json
|
|
165
|
-
semrush-cli auth logout
|
|
166
|
-
semrush-cli daemon stop
|
|
167
|
-
semrush-cli daemon start
|
|
168
|
-
```
|
|
203
|
+
the gifs use captured output for public keyword research and an offline project preview. no account details or credentials. the [capture notes and renderer](docs/demo/README.md) are in the repo if you want to regenerate them.
|
|
169
204
|
|
|
170
|
-
|
|
171
|
-
private daemon listens on `127.0.0.1:8771`. It continues after the terminal closes;
|
|
172
|
-
run `daemon start` after reboot. The CLI installs no system startup service.
|
|
205
|
+
## building from source
|
|
173
206
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
integration, so this tool does not invent one. See [architecture](docs/architecture.md)
|
|
183
|
-
and [SDK provenance](src/vendor/README.md).
|
|
184
|
-
|
|
185
|
-
| Variable | Default |
|
|
186
|
-
| --- | --- |
|
|
187
|
-
| `SEMRUSH_GALLETON_URL` | `http://127.0.0.1:8771` |
|
|
188
|
-
| `SEMRUSH_GALLETON_TOKEN_FILE` | `~/.local/share/semrush-cli/galleton/api.token` |
|
|
189
|
-
| `SEMRUSH_SESSION` | `semrush-cli` |
|
|
190
|
-
| `SEMRUSH_BROWSER_EXECUTABLE` | Patchright's Chromium |
|
|
191
|
-
|
|
192
|
-
The setup/daemon commands manage only the default CLI-owned daemon. An external
|
|
193
|
-
daemon must support the documented static-provider extension. Never expose its
|
|
194
|
-
API token or credential directory.
|
|
207
|
+
```bash
|
|
208
|
+
git clone https://github.com/Microck/semrush-cli.git
|
|
209
|
+
cd semrush-cli
|
|
210
|
+
npm ci
|
|
211
|
+
npm run build
|
|
212
|
+
node dist/cli.js --help
|
|
213
|
+
npm run check
|
|
214
|
+
```
|
|
195
215
|
|
|
196
|
-
|
|
216
|
+
the source build uses go to build the pinned galleton engine. npm releases include the engine for supported platforms.
|
|
197
217
|
|
|
198
|
-
|
|
199
|
-
can consume an allowance. Internal web endpoints may change. Provider caps remain
|
|
200
|
-
visible; there is no unlimited-export claim. Galleton encrypts at rest but its key
|
|
201
|
-
and vault share a private local directory. Other processes running as your OS user
|
|
202
|
-
are within the same trust boundary.
|
|
218
|
+
## documentation
|
|
203
219
|
|
|
204
|
-
|
|
220
|
+
full docs live at [semrush.micr.dev](https://semrush.micr.dev). setup, authentication, projects, keyword lists, site audits, mcp and the complete command reference.
|
|
205
221
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
npm --prefix docs-site run build
|
|
211
|
-
```
|
|
222
|
+
- [coverage](https://semrush.micr.dev/docs/coverage)
|
|
223
|
+
- [authentication](https://semrush.micr.dev/docs/authentication)
|
|
224
|
+
- [scripting](https://semrush.micr.dev/docs/scripting)
|
|
225
|
+
- [architecture](docs/architecture.md)
|
|
212
226
|
|
|
213
|
-
|
|
214
|
-
daemon with its storage-only extension. The build runs the upstream core suite
|
|
215
|
-
with the extension's integration test. Published users do not need Go.
|
|
227
|
+
## contributing
|
|
216
228
|
|
|
217
|
-
|
|
218
|
-
Chromium and Galleton with a local HTTP fixture, and exercise the built executable.
|
|
219
|
-
They never require a Semrush account. See [verification](docs/verification.md) for
|
|
220
|
-
the separate live-account evidence.
|
|
229
|
+
issues and pull requests are welcome. if semrush changes an endpoint, include the command you ran and the error. leave cookies and account details out of the report.
|
|
221
230
|
|
|
222
|
-
##
|
|
231
|
+
## disclaimer
|
|
223
232
|
|
|
224
|
-
|
|
225
|
-
semrush-cli auth logout
|
|
226
|
-
semrush-cli daemon stop
|
|
227
|
-
npm uninstall -g semrush-cli
|
|
228
|
-
```
|
|
233
|
+
this is an unofficial client, not affiliated with semrush. it uses the web product's internal endpoints, which can change. your subscription and account limits apply.
|
|
229
234
|
|
|
230
|
-
|
|
231
|
-
checking they contain nothing you need.
|
|
235
|
+
## license
|
|
232
236
|
|
|
233
|
-
|
|
237
|
+
[mit license](LICENSE)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 230" role="img" aria-labelledby="title">
|
|
2
|
+
<title id="title">semrush cli</title>
|
|
3
|
+
<style>.wordmark{fill:#421983}@media(prefers-color-scheme:dark){.wordmark{fill:#fff}}</style>
|
|
4
|
+
<g transform="translate(12 38) scale(.48)"> <path d="M357.481 3C333.269 3 306.873 11.7835 277.382 27.1205L113.044 114.41H247.71L3 244.524H272.376L136.164 316.977H361.44C446.818 314.792 515.038 245.48 515.038 160.011C515.038 74.5425 444.178 3.04552 357.481 3.04552V3Z" fill="#ff642d"/>
|
|
5
|
+
<path d="M136.164 316.977L272.376 244.524H408L272 316.977Z" fill="#a76bff"/>
|
|
6
|
+
<path d="m317 116 47 44-47 44m73 0h52" fill="none" stroke="#421983" stroke-width="22" stroke-linecap="square" stroke-linejoin="miter"/>
|
|
7
|
+
</g>
|
|
8
|
+
<g class="wordmark" transform="translate(287 153) scale(0.13079394217530976 -0.13079394217530976)"><path d="M283 -12Q218 -12 163.0 12.5Q108 37 72.5 82.0Q37 127 30 188H149Q154 157 175.0 133.0Q196 109 225.5 96.0Q255 83 287 83Q331 83 358.5 103.0Q386 123 386 155Q386 186 367.5 203.0Q349 220 319.0 229.5Q289 239 253.5 246.0Q218 253 182.0 262.5Q146 272 116.0 289.5Q86 307 67.5 337.5Q49 368 49 417Q49 468 76.0 506.5Q103 545 152.0 566.5Q201 588 266 588Q326 588 377.5 565.5Q429 543 463.0 502.5Q497 462 504 408H383Q373 445 340.0 469.5Q307 494 261 494Q219 494 194.0 476.0Q169 458 169 429Q169 402 187.5 387.5Q206 373 236.0 364.5Q266 356 302.0 349.0Q338 342 374.0 331.5Q410 321 440.0 302.0Q470 283 488.5 250.5Q507 218 507 166Q507 114 478.0 73.5Q449 33 398.5 10.5Q348 -12 283 -12Z M867 -12Q783 -12 719.5 28.0Q656 68 621.0 136.0Q586 204 586 288Q586 373 620.0 440.5Q654 508 714.5 548.0Q775 588 856 588Q941 588 1002.0 547.5Q1063 507 1091.5 436.5Q1120 366 1107 276H705Q707 225 726.5 184.0Q746 143 782.0 119.5Q818 96 867 96Q920 96 954.5 121.5Q989 147 1000 188H1122Q1105 94 1035.0 41.0Q965 -12 867 -12ZM712 365H990Q986 418 950.5 451.5Q915 485 860 485Q807 485 767.0 453.5Q727 422 712 365Z M1945 0V350Q1945 409 1914.5 444.0Q1884 479 1831 479Q1774 479 1741.0 435.0Q1708 391 1708 327V0H1587V350Q1587 409 1556.5 444.0Q1526 479 1473 479Q1416 479 1383.5 435.0Q1351 391 1351 327V0H1229V576H1335L1345 482Q1370 533 1412.0 560.5Q1454 588 1508 588Q1571 588 1618.5 558.5Q1666 529 1689 476Q1719 533 1764.5 560.5Q1810 588 1866 588Q1955 588 2010.5 531.0Q2066 474 2066 375V0Z M2204 0V576H2310L2319 484Q2332 526 2363.5 551.0Q2395 576 2438 576H2529L2543 470H2446Q2385 470 2355.5 424.5Q2326 379 2326 302V0Z M2826 -12Q2765 -12 2718.0 14.0Q2671 40 2645.0 88.0Q2619 136 2619 201V576H2740V226Q2740 167 2773.0 132.5Q2806 98 2861 98Q2921 98 2955.0 141.5Q2989 185 2989 249V576H3110V0H3005L2995 95Q2969 44 2925.5 16.0Q2882 -12 2826 -12Z M3465 -12Q3400 -12 3345.0 12.5Q3290 37 3254.5 82.0Q3219 127 3212 188H3331Q3336 157 3357.0 133.0Q3378 109 3407.5 96.0Q3437 83 3469 83Q3513 83 3540.5 103.0Q3568 123 3568 155Q3568 186 3549.5 203.0Q3531 220 3501.0 229.5Q3471 239 3435.5 246.0Q3400 253 3364.0 262.5Q3328 272 3298.0 289.5Q3268 307 3249.5 337.5Q3231 368 3231 417Q3231 468 3258.0 506.5Q3285 545 3334.0 566.5Q3383 588 3448 588Q3508 588 3559.5 565.5Q3611 543 3645.0 502.5Q3679 462 3686 408H3565Q3555 445 3522.0 469.5Q3489 494 3443 494Q3401 494 3376.0 476.0Q3351 458 3351 429Q3351 402 3369.5 387.5Q3388 373 3418.0 364.5Q3448 356 3484.0 349.0Q3520 342 3556.0 331.5Q3592 321 3622.0 302.0Q3652 283 3670.5 250.5Q3689 218 3689 166Q3689 114 3660.0 73.5Q3631 33 3580.5 10.5Q3530 -12 3465 -12Z M3923 748V494Q3950 539 3991.5 563.5Q4033 588 4085 588Q4147 588 4194.0 562.0Q4241 536 4267.0 488.5Q4293 441 4293 375V0H4171V350Q4171 410 4138.0 444.0Q4105 478 4050 478Q3990 478 3956.5 434.5Q3923 391 3923 327V0H3801V748Z"/></g>
|
|
9
|
+
<text x="289" y="202" font-family="monospace" font-size="18" letter-spacing="3" fill="#a76bff">cli</text>
|
|
10
|
+
</svg>
|
package/assets/logo.png
CHANGED
|
Binary file
|
package/assets/logo.svg
CHANGED
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 518 320" role="img" aria-labelledby="title">
|
|
2
|
-
<title id="title">semrush
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
<path d="m340 130 29 29-29 29m45 0h35" fill="none" stroke="black" stroke-width="12" stroke-linejoin="miter"/>
|
|
7
|
-
</mask>
|
|
8
|
-
</defs>
|
|
9
|
-
<path d="M357.481 3C333.269 3 306.873 11.7835 277.382 27.1205L113.044 114.41H247.71L3 244.524H272.376L136.164 316.977H361.44C446.818 314.792 515.038 245.48 515.038 160.011C515.038 74.5425 444.178 3.04552 357.481 3.04552V3Z" fill="#ff642d" mask="url(#terminal)"/>
|
|
2
|
+
<title id="title">semrush terminal comet</title>
|
|
3
|
+
<path d="M357.481 3C333.269 3 306.873 11.7835 277.382 27.1205L113.044 114.41H247.71L3 244.524H272.376L136.164 316.977H361.44C446.818 314.792 515.038 245.48 515.038 160.011C515.038 74.5425 444.178 3.04552 357.481 3.04552V3Z" fill="#ff642d"/>
|
|
4
|
+
<path d="M136.164 316.977L272.376 244.524H408L272 316.977Z" fill="#a76bff"/>
|
|
5
|
+
<path d="m317 116 47 44-47 44m73 0h52" fill="none" stroke="#421983" stroke-width="22" stroke-linecap="square" stroke-linejoin="miter"/>
|
|
10
6
|
</svg>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as SessionError, r as UsageError } from "./errors-
|
|
2
|
-
import { a as Sessions, r as origin } from "./session-layer-
|
|
1
|
+
import { n as SessionError, r as UsageError } from "./errors-DNn_XeoU.js";
|
|
2
|
+
import { a as Sessions, r as origin } from "./session-layer-Bq5OpnR5.js";
|
|
3
3
|
import "./browser-service-BYYE-vSm.js";
|
|
4
|
-
import { t as readBootstrap } from "./browser-layer-
|
|
4
|
+
import { t as readBootstrap } from "./browser-layer-gdm03TCV.js";
|
|
5
5
|
import { Config, Effect, Option } from "effect";
|
|
6
6
|
import { chromium } from "patchright";
|
|
7
7
|
|
|
@@ -79,4 +79,4 @@ const login = Effect.fn("Auth.login")(function* () {
|
|
|
79
79
|
|
|
80
80
|
//#endregion
|
|
81
81
|
export { importBrowser, login };
|
|
82
|
-
//# sourceMappingURL=auth-
|
|
82
|
+
//# sourceMappingURL=auth-CrYOA8QT.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-
|
|
1
|
+
{"version":3,"file":"auth-CrYOA8QT.js","names":["page","browser"],"sources":["../src/auth.ts"],"sourcesContent":["import { Config, Effect, Option } from 'effect';\nimport { chromium, type BrowserContext } from 'patchright';\nimport { Sessions } from './session-service.js';\nimport { origin } from './session-layer.js';\nimport { SessionError, UsageError } from './errors.js';\nimport { readBootstrap } from './browser-layer.js';\n\nconst authFailed = () => new SessionError({ message: 'Could not import a signed-in Semrush session. Check the browser, complete sign-in, and retry. No credentials were logged.' });\nconst authCall = <A>(run: () => Promise<A>) => Effect.tryPromise({ try: run, catch: authFailed });\n\nconst saveContext = Effect.fn('Auth.saveContext')(function* (context: BrowserContext) {\n const sessions = yield* Sessions;\n const page = yield* Effect.acquireRelease(authCall(() => context.newPage()), page => Effect.promise(() => page.close()));\n yield* authCall(() => page.goto(origin + '/robots.txt', { waitUntil: 'domcontentloaded', timeout: 45000 }));\n const html = yield* authCall(() => page.evaluate(async () => {\n const response = await fetch('/analytics/overview/?db=us', { credentials: 'include', signal: AbortSignal.timeout(45000) });\n return response.ok ? response.text() : '';\n }));\n yield* Effect.try({ try: () => readBootstrap(html), catch: authFailed });\n const cookies = yield* authCall(() => context.cookies(origin));\n if (!cookies.length) return yield* Effect.fail(authFailed());\n // Bare cookies are intentionally bound to the single approved origin by Galleton.\n return yield* sessions.save({ provider: 'semrush', cookie_origin: origin, cookie_header: cookies.map(cookie => `${cookie.name}=${cookie.value}`).join('; ') });\n});\n\nexport const importBrowser = Effect.fn('Auth.import')(function* (url: string) {\n const valid = yield* Effect.try({ try: () => {\n const parsed = new URL(url);\n if (parsed.protocol !== 'http:' || !['127.0.0.1', '[::1]'].includes(parsed.hostname) || parsed.username || parsed.password) throw new Error('Not loopback');\n return parsed.origin;\n }, catch: () => new UsageError({ message: 'CDP must be a literal loopback HTTP URL. Forward remote browsers over SSH first.' }) });\n const browser = yield* authCall(() => chromium.connectOverCDP(valid, { timeout: 30000 }));\n // Closing a CDP connection disconnects this client and leaves the existing browser running.\n yield* Effect.addFinalizer(() => Effect.promise(() => browser.close()));\n const context = browser.contexts()[0];\n if (!context) return yield* Effect.fail(authFailed());\n const status = yield* saveContext(context);\n return { logged_in: true, source: 'galleton', session: status.id };\n});\n\nexport const login = Effect.fn('Auth.login')(function* () {\n const executable = yield* Config.option(Config.String('SEMRUSH_BROWSER_EXECUTABLE'));\n const browser = yield* Effect.acquireRelease(authCall(() => chromium.launch({ headless: false, executablePath: Option.getOrUndefined(executable) })), browser => Effect.promise(() => browser.close()));\n const context = yield* authCall(() => browser.newContext({ userAgent: 'OpenAI File Downloader, XaiImageApiFetch/1.0' }));\n const page = yield* authCall(() => context.newPage());\n yield* authCall(() => page.goto(origin + '/login/', { waitUntil: 'domcontentloaded', timeout: 45000 }));\n yield* Effect.sync(() => process.stderr.write('Sign into Semrush in the browser. Waiting up to 5 minutes.\\n'));\n yield* authCall(() => page.waitForURL(url => url.hostname === 'www.semrush.com' && !url.pathname.startsWith('/login') && !url.pathname.startsWith('/signup'), { timeout: 300000 }));\n const status = yield* saveContext(context);\n return { logged_in: true, source: 'galleton', session: status.id };\n});\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { a as reportParameters, l as webRequest, n as SessionError, s as rpcBody, t as ReportError } from "./errors-
|
|
2
|
-
import { a as Sessions, r as origin } from "./session-layer-
|
|
1
|
+
import { a as reportParameters, l as webRequest, n as SessionError, s as rpcBody, t as ReportError } from "./errors-DNn_XeoU.js";
|
|
2
|
+
import { a as Sessions, r as origin } from "./session-layer-Bq5OpnR5.js";
|
|
3
3
|
import { t as Semrush } from "./browser-service-BYYE-vSm.js";
|
|
4
4
|
import { Config, Effect, Layer, Option, Predicate, Schema, Semaphore } from "effect";
|
|
5
5
|
import { chromium } from "patchright";
|
|
@@ -19,7 +19,7 @@ const json = JSONbig({
|
|
|
19
19
|
protoAction: "error",
|
|
20
20
|
constructorAction: "error"
|
|
21
21
|
});
|
|
22
|
-
const safeBrowserError = () => new ReportError({ message: "Semrush browser request failed. Run semrush
|
|
22
|
+
const safeBrowserError = () => new ReportError({ message: "Semrush browser request failed. Run semrush doctor. If your session expired, sign in again with semrush auth login." });
|
|
23
23
|
const browserCall = (run) => Effect.tryPromise({
|
|
24
24
|
try: run,
|
|
25
25
|
catch: safeBrowserError
|
|
@@ -74,7 +74,7 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
74
74
|
}));
|
|
75
75
|
const auth = yield* Effect.try({
|
|
76
76
|
try: () => readBootstrap(bootstrap.text),
|
|
77
|
-
catch: () => new SessionError({ message: "Semrush sign-in metadata unavailable. Run semrush
|
|
77
|
+
catch: () => new SessionError({ message: "Semrush sign-in metadata unavailable. Run semrush auth login or import a signed-in browser." })
|
|
78
78
|
});
|
|
79
79
|
const semaphore = yield* Semaphore.make(1);
|
|
80
80
|
let spectrumToken = "";
|
|
@@ -180,7 +180,7 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
180
180
|
signal: AbortSignal.timeout(15e3)
|
|
181
181
|
});
|
|
182
182
|
return response.ok && new URL(response.url).pathname === "/home/";
|
|
183
|
-
})))) return yield* Effect.fail(new SessionError({ message: "Semrush session expired. Run semrush
|
|
183
|
+
})))) return yield* Effect.fail(new SessionError({ message: "Semrush session expired. Run semrush auth login or import a signed-in browser." }));
|
|
184
184
|
const family = definition.transport;
|
|
185
185
|
const planned = ["project_web", "audit_web"].includes(family) ? webRequest(definition, query) : void 0;
|
|
186
186
|
const params = planned?.body ?? reportParameters(definition, query);
|
|
@@ -310,4 +310,4 @@ const captureCookies = Effect.fn("Semrush.captureCookies")(function* (context, c
|
|
|
310
310
|
|
|
311
311
|
//#endregion
|
|
312
312
|
export { semrushLive as n, readBootstrap as t };
|
|
313
|
-
//# sourceMappingURL=browser-layer-
|
|
313
|
+
//# sourceMappingURL=browser-layer-gdm03TCV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-layer-gdm03TCV.js","names":["browser","payload: Schema.JsonObject | null","method","payload","options: RequestInit","extraHeaders","path","extra: Record<string, string>","reportData: Schema.Json | undefined","request"],"sources":["../src/browser-layer.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { Config, Effect, Layer, Option, Semaphore, Schema, Predicate } from 'effect';\nimport { chromium, type BrowserContext, type Page } from 'patchright';\nimport JSONbig from 'json-bigint';\nimport { Semrush } from './browser-service.js';\nimport { Sessions } from './session-service.js';\nimport { origin } from './session-layer.js';\nimport { ReportError, SessionError } from './errors.js';\nimport { reportParameters, webRequest, rpcBody, type Query } from './report-parameters.js';\nimport type { ReportRequest } from './validation.js';\n\nconst transportPaths = new Map([['domain', '/dpa/rpc'], ['direct', '/dpa/rpc'], ['magic', '/kmtgw/v2/webapi'], ['keyword', '/kwogw/v2/webapi'], ['kwm', '/kwm/rpc']]);\nconst json = JSONbig({ storeAsString: true, protoAction: 'error', constructorAction: 'error' });\nconst safeBrowserError = () => new ReportError({ message: 'Semrush browser request failed. Run semrush doctor. If your session expired, sign in again with semrush auth login.' });\nconst browserCall = <A>(run: () => Promise<A>) => Effect.tryPromise({ try: run, catch: safeBrowserError });\nconst objectValue = (value: Schema.Json | undefined): value is Schema.JsonObject => Predicate.isObject(value) && !Array.isArray(value);\n\n/** JSON never passes through the browser's number parser, preserving 64-bit IDs. */\nexport function parseWire(text: string): Schema.Json { return Schema.decodeUnknownSync(Schema.Json)(json.parse(text)); }\n\n/** Remove credential fields and echoed secrets, including nested provider errors. */\nexport function redact(value: Schema.Json, secrets: string[]): Schema.Json {\n if (Array.isArray(value)) return value.map(item => redact(item, secrets));\n if (objectValue(value)) return Object.fromEntries(Object.entries(value).filter(([key]) => !/^(api_?key|key|user_?id|access_token|refresh_token|authorization|cookie|set-cookie)$/i.test(key)).map(([key, item]) => [key, redact(item, secrets)]));\n if (Predicate.isString(value)) return secrets.filter(secret => secret.length > 0).reduce((text, secret) => text.replaceAll(secret, '[redacted]'), value);\n return value;\n}\n\nexport const makeSemrushLayer = (browserOrigin: string) => Layer.effect(Semrush, Effect.gen(function* () {\n const sessions = yield* Sessions;\n const executable = yield* Config.option(Config.String('SEMRUSH_BROWSER_EXECUTABLE'));\n const headers = yield* sessions.headers();\n const browser = yield* Effect.acquireRelease(browserCall(() => chromium.launch({ headless: true, executablePath: Option.getOrUndefined(executable) })), browser => Effect.promise(() => browser.close()));\n const context = yield* browserCall(() => browser.newContext({ userAgent: 'OpenAI File Downloader, XaiImageApiFetch/1.0' }));\n const cookieHeader = headers.Cookie ?? headers.cookie ?? '';\n yield* browserCall(() => context.addCookies(cookieHeader.split(';').filter(cookie => cookie.includes('=')).map(cookie => {\n const separator = cookie.indexOf('=');\n return { name: cookie.slice(0, separator).trim(), value: cookie.slice(separator + 1).trim(), url: browserOrigin, httpOnly: true, secure: browserOrigin.startsWith('https:'), sameSite: 'Lax' as const };\n })));\n const page = yield* browserCall(() => context.newPage());\n yield* browserCall(() => page.goto(browserOrigin + '/robots.txt', { waitUntil: 'domcontentloaded', timeout: 45000 }));\n const bootstrap = yield* browserCall(() => page.evaluate(async () => {\n const response = await fetch('/analytics/overview/?db=us', { credentials: 'include', signal: AbortSignal.timeout(45000) });\n return { status: response.status, text: await response.text() };\n }));\n // Extract the JSON value with a balanced scanner, never eval page-owned JavaScript.\n const auth = yield* Effect.try({ try: () => readBootstrap(bootstrap.text), catch: () => new SessionError({ message: 'Semrush sign-in metadata unavailable. Run semrush auth login or import a signed-in browser.' }) });\n const semaphore = yield* Semaphore.make(1);\n let spectrumToken = '';\n const request = Effect.fn('Semrush.request')(function* (path: string, payload: Query | null, method = 'POST', extraHeaders: Record<string, string> = {}) {\n const wire = yield* browserCall(() => page.evaluate(async ({ path, encodedPayload, method, extraHeaders }) => {\n const payload: Schema.JsonObject | null = JSON.parse(encodedPayload);\n const query = new URLSearchParams();\n const get = method === 'GET';\n if (get) for (const [key, value] of Object.entries(payload ?? {})) for (const item of Array.isArray(value) ? value : [value]) query.append(key, String(item));\n const options: RequestInit = { method, credentials: 'include', headers: extraHeaders, signal: AbortSignal.timeout(45000) };\n if (!get && method !== 'DELETE' && payload !== null) { options.headers = { ...extraHeaders, 'Content-Type': 'application/json' }; options.body = JSON.stringify(payload); }\n const response = await fetch(get && query.size > 0 ? path + (path.includes('?') ? '&' : '?') + query : path, options);\n return { status: response.status, text: await response.text() };\n }, { path, encodedPayload: JSON.stringify(payload), method, extraHeaders }));\n if (wire.status < 200 || wire.status >= 300) {\n const detail = yield* Effect.try({ try: () => JSON.stringify(redact(parseWire(wire.text), [auth.apiKey, spectrumToken, spectrumToken.replace(/^Bearer /, '')])).slice(0, 500), catch: safeBrowserError }).pipe(Effect.option);\n return yield* Effect.fail(new ReportError({ message: `Semrush request returned HTTP ${wire.status}.${Option.isSome(detail) ? ' ' + detail.value : ' Check your session and account permissions.'}` }));\n }\n if (wire.status === 204 && !wire.text) return null;\n const body = yield* Effect.try({ try: () => parseWire(wire.text), catch: () => new ReportError({ message: 'Semrush returned a page or challenge instead of JSON. Sign in again or retry later.' }) });\n if (objectValue(body) && ('error' in body || (!path.startsWith('/siteaudit/') && 'status' in body && !(body.status === null || body.status === 'SUCCESS' || body.status === 'success')))) return yield* Effect.fail(new ReportError({ message: `Semrush report failed: ${JSON.stringify(redact(body.error ?? body.status ?? null, [auth.apiKey, spectrumToken, spectrumToken.replace(/^Bearer /, '')])).slice(0, 500)}` }));\n return body;\n });\n const rpc = Effect.fn('Semrush.rpc')(function* (method: string, params: Query, transport = 'keyword', webReport = 'domain.overview') {\n const payload = transport === 'domain' ? { request_id: randomUUID(), report: webReport, args: params, ...auth } : transport === 'direct' ? { ...params, ...auth } : params;\n const rpcParams = transport === 'kwm' ? { ...payload, ...(method.startsWith('user.') ? { api_key: auth.apiKey, user_id: auth.userId } : { APIKey: auth.apiKey, UserID: auth.userId }) } : payload;\n const extra: Record<string, string> = transport === 'kwm' ? { 'api-key': auth.apiKey, 'user-id': String(auth.userId) } : {};\n const body = yield* request(transportPaths.get(transport) ?? '/kwogw/v2/webapi', rpcBody(method, rpcParams), 'POST', extra);\n if (!objectValue(body) || !('result' in body)) return yield* Effect.fail(new ReportError({ message: `Semrush ${method}: missing report result.` }));\n return body.result ?? null;\n });\n const checkAuditSettings = Effect.fn('Semrush.checkAuditSettings')(function* (name: string, query: Query) {\n if (name === 'audit_create') {\n const campaigns = yield* request('/siteaudit/api/campaigns/seolist', { key: auth.apiKey }, 'GET');\n if (!Array.isArray(campaigns)) return yield* Effect.fail(new ReportError({ message: 'Site Audit campaign discovery returned an unexpected response. No settings were saved.' }));\n if (campaigns.some(campaign => objectValue(campaign) && String(campaign.id) === String(query.campaign_id))) return yield* Effect.fail(new ReportError({ message: 'This SEO project already has a Site Audit. Use audit-configure to replace its settings explicitly.' }));\n }\n // The audit save endpoint replaces settings, including protected-site credentials.\n if (name === 'audit_configure') {\n const current = yield* request(`/siteaudit/api/campaigns/${query.campaign_id}/info?key=${encodeURIComponent(auth.apiKey)}`, {}, 'GET');\n if (objectValue(current) && Predicate.isString(current.projectDomain) && current.projectDomain !== query.domain) return yield* Effect.fail(new ReportError({ message: 'The audit domain must match the SEO project website. Inspect audit-info for projectDomain before configuring it.' }));\n if (objectValue(current) && (Boolean(current.authEnabled) || Boolean(current.webBotAuthEnabled) || (objectValue(current.gaSettings) && Object.keys(current.gaSettings).length > 0))) return yield* Effect.fail(new ReportError({ message: 'This audit uses protected-site authentication or Google Analytics. Edit its settings in the Semrush web UI to preserve those connections.' }));\n }\n });\n const report = Effect.fn('Semrush.report')(function* ({ name, definition, query }: ReportRequest) {\n const valid = yield* browserCall(() => page.evaluate(async () => {\n const response = await fetch('/home/', { method: 'HEAD', credentials: 'include', cache: 'no-store', signal: AbortSignal.timeout(15000) });\n return response.ok && new URL(response.url).pathname === '/home/';\n }));\n if (!valid) return yield* Effect.fail(new SessionError({ message: 'Semrush session expired. Run semrush auth login or import a signed-in browser.' }));\n const family = definition.transport;\n const planned = ['project_web', 'audit_web'].includes(family) ? webRequest(definition, query) : undefined;\n const params = planned?.body ?? reportParameters(definition, query);\n let reportData: Schema.Json | undefined;\n if (planned) {\n // The web UI only allows adding a website to a project that has none.\n if (name === 'project_update' && query.domain !== undefined) {\n const current = yield* request('/apis/v4-raw/folders/api/v0/folders/folder', { folder_id: query.folder_id ?? null }, 'GET');\n if (!objectValue(current) || !objectValue(current.folder)) return yield* Effect.fail(new ReportError({ message: 'Project metadata unavailable. Inspect the project before adding a website.' }));\n if (Predicate.isString(current.folder.domain) && current.folder.domain.length > 0) return yield* Effect.fail(new ReportError({ message: 'This project already has a website. The Semrush web UI only allows adding a website when none is set.' }));\n }\n if (family === 'audit_web') yield* checkAuditSettings(name, query);\n const path = family === 'audit_web' ? planned.path + '?key=' + encodeURIComponent(auth.apiKey) : planned.path;\n reportData = yield* request(path, planned.body, planned.method);\n } else if (['keyword', 'domain', 'direct', 'magic', 'kwm'].includes(family)) reportData = yield* rpc(definition.method, params, family, definition.web_report);\n else {\n const extra: Record<string, string> = {};\n if (family === 'gap') {\n if (!spectrumToken) spectrumToken = yield* browserCall(() => observeSpectrum(page, browserOrigin));\n Object.assign(extra, { authorization: spectrumToken, 'x-report': 'gap.keywords', 'x-request-id': randomUUID() });\n }\n let path = definition.method;\n if (family.startsWith('backlink')) {\n path = '/analytics/backlinks/webapi2/';\n params.key = auth.apiKey;\n if (family === 'backlink_overview') {\n path += 'overview/' + definition.method;\n extra['x-request-id'] = randomUUID();\n yield* request('/analytics/backlinks/webapi2/overview/init', params, 'GET', extra);\n }\n } else if (path.startsWith('/siteaudit/')) params.key = auth.apiKey;\n reportData = yield* request(path, params, ['post', 'gap', 'projects'].includes(family) ? 'POST' : 'GET', extra);\n }\n if (definition.total_method) {\n const totalParams = name === 'domain_position_changes' ? Object.fromEntries(Object.entries(params).filter(([key]) => key !== 'display')) : params;\n const total = yield* rpc(definition.total_method, totalParams, 'domain', definition.web_report);\n reportData = { rows: reportData, total, page: query.page ?? null, page_size: query.page_size ?? null };\n }\n if (name === 'keyword_overview') {\n const rows = objectValue(reportData) ? reportData.keywords : undefined;\n reportData = Array.isArray(rows) ? rows.find(item => objectValue(item) && item.database === query.database) : undefined;\n if (!reportData) return yield* Effect.fail(new ReportError({ message: 'No keyword data for the selected database.' }));\n } else if (name === 'domain_overview') {\n const search = Array.isArray(reportData) ? reportData.find(item => objectValue(item) && item.database === query.database) : undefined;\n if (!search) return yield* Effect.fail(new ReportError({ message: 'No domain data for the selected database.' }));\n reportData = { search, backlinks: yield* rpc('backlinks.Summary', { searchItem: query.domain ?? null, searchType: 'domain' }, 'domain') };\n }\n if (Predicate.isNumber(query.limit) && Array.isArray(reportData)) reportData = reportData.slice(0, query.limit);\n yield* captureCookies(context, sessions.capture, browserOrigin);\n return { authenticated: true as const, report: name, data: redact(reportData ?? null, [auth.apiKey, spectrumToken, spectrumToken.replace(/^Bearer /, '')]) };\n });\n return Semrush.of({ report: request => semaphore.withPermits(1)(report(request)) });\n}));\n\nexport const semrushLive = makeSemrushLayer(origin);\n\ninterface BootstrapAuth { readonly apiKey: string; readonly userId: string | number; }\nexport function readBootstrap(html: string): BootstrapAuth {\n const match = /window\\.sm2\\.user\\s*=\\s*/.exec(html);\n if (!match) throw new Error('No bootstrap');\n const start = match.index + match[0].length;\n let depth = 0, quoted = false, escaped = false;\n for (let index = start; index < html.length; index++) {\n const char = html[index];\n if (quoted) {\n if (escaped) escaped = false;\n else if (char === '\\\\') escaped = true;\n else if (char === '\"') quoted = false;\n } else if (char === '\"') quoted = true;\n else if (char === '{') depth++;\n else if (char === '}' && --depth === 0) {\n const user = parseWire(html.slice(start, index + 1));\n if (objectValue(user) && Predicate.isString(user.api_key) && (Predicate.isString(user.id) || Predicate.isNumber(user.id))) return { apiKey: user.api_key, userId: user.id };\n break;\n }\n }\n throw new Error('Invalid bootstrap');\n}\n\nasync function observeSpectrum(page: Page, browserOrigin: string): Promise<string> {\n try {\n // Observe and navigate together so either timeout is handled immediately.\n const [observed] = await Promise.all([\n page.waitForRequest(request => request.url().startsWith(browserOrigin + '/spectrum/') && Boolean(request.headers().authorization), { timeout: 20000 }),\n page.goto(browserOrigin + '/analytics/keywordgap/?db=us', { waitUntil: 'domcontentloaded', timeout: 45000 }),\n ]);\n return observed.headers().authorization ?? '';\n } finally {\n await page.goto(browserOrigin + '/robots.txt', { waitUntil: 'domcontentloaded', timeout: 45000 });\n }\n}\n\nconst captureCookies = Effect.fn('Semrush.captureCookies')(function* (context: BrowserContext, capture: (cookies: string[]) => Effect.Effect<void, SessionError>, cookieOrigin: string) {\n const cookies = yield* browserCall(() => context.cookies(cookieOrigin));\n yield* capture(cookies.map(cookie => `${cookie.name}=${cookie.value}; Path=${cookie.path}${cookieOrigin.startsWith('https:') ? '; Secure' : ''}${cookie.httpOnly ? '; HttpOnly' : ''}${cookie.expires > 0 ? '; Expires=' + new Date(cookie.expires * 1000).toUTCString() : ''}`));\n});\n"],"mappings":"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"}
|