semrush-cli 0.1.0 → 0.2.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 +81 -41
- package/THIRD-PARTY-NOTICES.md +4 -2
- package/assets/logo.png +0 -0
- package/assets/logo.svg +10 -1
- package/dist/{auth-B26NjmuZ.js → auth-CYAtam3G.js} +4 -4
- package/dist/{auth-B26NjmuZ.js.map → auth-CYAtam3G.js.map} +1 -1
- package/dist/{browser-layer-DijkeDly.js → browser-layer-CY1KdjJo.js} +41 -21
- package/dist/browser-layer-CY1KdjJo.js.map +1 -0
- package/dist/cli.js +26 -19
- package/dist/cli.js.map +1 -1
- package/dist/{errors-aN5dmXgv.js → errors-CJuwKbEZ.js} +681 -49
- package/dist/errors-CJuwKbEZ.js.map +1 -0
- package/dist/{mcp-DUyKjMzQ.js → mcp-CSgmSxRc.js} +15 -9
- package/dist/mcp-CSgmSxRc.js.map +1 -0
- package/dist/{session-layer-u9NMfM8W.js → session-layer-BIL6Umvb.js} +5 -5
- package/dist/{session-layer-u9NMfM8W.js.map → session-layer-BIL6Umvb.js.map} +1 -1
- package/dist/{validation-BIbDUKRg.js → validation-BCtts-Al.js} +7 -5
- package/dist/validation-BCtts-Al.js.map +1 -0
- package/dist/validation-DBzNMr6w.js +4 -0
- package/package.json +1 -2
- package/dist/browser-layer-DijkeDly.js.map +0 -1
- package/dist/errors-aN5dmXgv.js.map +0 -1
- package/dist/mcp-DUyKjMzQ.js.map +0 -1
- package/dist/validation-BIbDUKRg.js.map +0 -1
- package/dist/validation-D4jV0Q8q.js +0 -4
package/README.md
CHANGED
|
@@ -1,23 +1,40 @@
|
|
|
1
|
-
<p align="center"
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/logo.svg" width="220" alt="semrush-cli">
|
|
3
|
+
</p>
|
|
2
4
|
<h1 align="center">semrush-cli</h1>
|
|
3
|
-
<p align="center">
|
|
4
|
-
<
|
|
5
|
+
<p align="center">
|
|
6
|
+
<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"></a>
|
|
7
|
+
<a href="https://www.npmjs.com/package/semrush-cli"><img src="https://img.shields.io/npm/v/semrush-cli?style=flat-square&color=000000" alt="npm"></a>
|
|
8
|
+
<a href="https://github.com/Microck/semrush-cli/releases/latest"><img src="https://img.shields.io/github/v/release/Microck/semrush-cli?style=flat-square&color=000000" alt="release"></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/github/license/Microck/semrush-cli?style=flat-square&color=000000" alt="license"></a>
|
|
10
|
+
</p>
|
|
5
11
|
|
|
6
|
-
|
|
7
|
-
keyword and backlink gaps, backlinks, historical/mobile research and AI visibility.
|
|
8
|
-
Written in TypeScript with Effect. Sessions live in an encrypted Galleton vault.
|
|
9
|
-
No Semrush developer API key, Python runtime or CAPTCHA service required.
|
|
12
|
+
---
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
semrush from your terminal and MCP clients. keyword research, organic rankings,
|
|
15
|
+
competitors, backlinks and AI visibility, through your signed-in web account.
|
|
16
|
+
built with TypeScript + Effect. sessions stored in Galleton.
|
|
17
|
+
|
|
18
|
+
[docs](https://semrush.micr.dev) · [npm](https://www.npmjs.com/package/semrush-cli) · [mcp](#mcp) · [issues](https://github.com/Microck/semrush-cli/issues)
|
|
19
|
+
|
|
20
|
+
## why
|
|
21
|
+
|
|
22
|
+
62 operations with the same input schemas in the CLI and MCP. JSON output for
|
|
23
|
+
scripts, account totals and pagination, project management, and encrypted session storage.
|
|
24
|
+
no Semrush developer API key or Python runtime required.
|
|
25
|
+
|
|
26
|
+
The executable is `semrush-cli`. It can coexist with other tools named `semrush`.
|
|
27
|
+
|
|
28
|
+
## quickstart
|
|
12
29
|
|
|
13
30
|
Requires Node.js 22+. Includes Galleton binaries for Linux x64/arm64,
|
|
14
31
|
macOS x64/arm64 and Windows x64.
|
|
15
32
|
|
|
16
33
|
```sh
|
|
17
34
|
npm install -g semrush-cli
|
|
18
|
-
semrush setup
|
|
19
|
-
semrush auth login
|
|
20
|
-
semrush domain-overview --domain openai.com --json
|
|
35
|
+
semrush-cli setup
|
|
36
|
+
semrush-cli auth login
|
|
37
|
+
semrush-cli domain-overview --domain openai.com --json
|
|
21
38
|
```
|
|
22
39
|
|
|
23
40
|
`setup` installs Chromium and starts a private Galleton daemon. `auth login`
|
|
@@ -28,8 +45,8 @@ libraries can be installed with `npx patchright install-deps chromium`.
|
|
|
28
45
|
To import an existing, authorized Chromium session on a headless machine:
|
|
29
46
|
|
|
30
47
|
```sh
|
|
31
|
-
semrush auth import --cdp-url http://127.0.0.1:9234
|
|
32
|
-
semrush doctor --json
|
|
48
|
+
semrush-cli auth import --cdp-url http://127.0.0.1:9234
|
|
49
|
+
semrush-cli doctor --json
|
|
33
50
|
```
|
|
34
51
|
|
|
35
52
|
The browser must already expose CDP on a literal loopback HTTP address. This
|
|
@@ -37,20 +54,40 @@ supports the browser managed by [semrush-web-mcp](https://github.com/Microck/sem
|
|
|
37
54
|
Import does not stop or change that browser. Keep CDP private; forward it over
|
|
38
55
|
SSH when necessary.
|
|
39
56
|
|
|
40
|
-
##
|
|
57
|
+
## projects
|
|
58
|
+
|
|
59
|
+
```sh
|
|
60
|
+
semrush-cli projects --json
|
|
61
|
+
semrush-cli project-create --name 'my website' --domain example.com --dry-run
|
|
62
|
+
semrush-cli project-create --name 'my website' --domain example.com --confirm --json
|
|
63
|
+
semrush-cli project-get --folder-id 123 --json
|
|
64
|
+
semrush-cli project-update --folder-id 123 --name 'new name' --confirm
|
|
65
|
+
semrush-cli project-set-tags --folder-id 123 --tags '["client"]' --confirm
|
|
66
|
+
semrush-cli project-pin --folder-id 123 --pinned --confirm
|
|
67
|
+
semrush-cli project-delete --folder-id 123 --confirm
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Project commands use the current web UI's folder IDs. Creation does not start
|
|
71
|
+
crawls or share the project. Every write requires `--confirm`; `--dry-run` prints
|
|
72
|
+
the request without authentication or network access. SEO project creation and
|
|
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).
|
|
76
|
+
|
|
77
|
+
## commands
|
|
41
78
|
|
|
42
79
|
```sh
|
|
43
|
-
semrush keyword-overview --keyword 'email marketing' --json
|
|
44
|
-
semrush keyword-magic --keyword 'email marketing' --questions-only --page-size 20 --json
|
|
45
|
-
semrush domain-keywords --domain openai.com --page 2 --page-size 20 --json
|
|
46
|
-
semrush domain-competitors --domain openai.com --page-size 10 --json
|
|
47
|
-
semrush keyword-gap --domains '["openai.com","anthropic.com"]' --json
|
|
48
|
-
semrush backlink-gap --domains '["openai.com","anthropic.com"]' --json
|
|
49
|
-
semrush backlink-links --domain openai.com --filters '+|type||nofollow' --json
|
|
50
|
-
semrush domain-ai-visibility --domain openai.com --json
|
|
51
|
-
semrush snapshot-dates --database us --json
|
|
52
|
-
semrush reports --json
|
|
53
|
-
semrush domain-keywords --help
|
|
80
|
+
semrush-cli keyword-overview --keyword 'email marketing' --json
|
|
81
|
+
semrush-cli keyword-magic --keyword 'email marketing' --questions-only --page-size 20 --json
|
|
82
|
+
semrush-cli domain-keywords --domain openai.com --page 2 --page-size 20 --json
|
|
83
|
+
semrush-cli domain-competitors --domain openai.com --page-size 10 --json
|
|
84
|
+
semrush-cli keyword-gap --domains '["openai.com","anthropic.com"]' --json
|
|
85
|
+
semrush-cli backlink-gap --domains '["openai.com","anthropic.com"]' --json
|
|
86
|
+
semrush-cli backlink-links --domain openai.com --filters '+|type||nofollow' --json
|
|
87
|
+
semrush-cli domain-ai-visibility --domain openai.com --json
|
|
88
|
+
semrush-cli snapshot-dates --database us --json
|
|
89
|
+
semrush-cli reports --json
|
|
90
|
+
semrush-cli domain-keywords --help
|
|
54
91
|
```
|
|
55
92
|
|
|
56
93
|
| Area | Reports |
|
|
@@ -60,7 +97,8 @@ semrush domain-keywords --help
|
|
|
60
97
|
| Gaps | Keyword overlaps and gaps, backlink prospects and profile comparisons |
|
|
61
98
|
| Backlinks | Links, referring domains, anchors, indexed pages, outbound domains, history and authority |
|
|
62
99
|
| AI visibility | Visibility, mentions, citation sources and country distribution |
|
|
63
|
-
|
|
|
100
|
+
| Projects | Create, inspect, rename, delete, leave, tags, pinning, toolkit status, dashboard metrics and explicit SEO setup |
|
|
101
|
+
| Account | Limits, permissions, databases, snapshots, folders, saved lists and audit campaigns |
|
|
64
102
|
|
|
65
103
|
All successful reports use `{authenticated: true, report, data}`. Domain ranking
|
|
66
104
|
and page reports include provider totals. Large integers remain exact as decimal
|
|
@@ -68,7 +106,7 @@ strings. Errors go to stderr with a nonzero exit. `--json` works before or after
|
|
|
68
106
|
the command. Objects and arrays use JSON flags; booleans support explicit `false`.
|
|
69
107
|
See the [full command reference](https://semrush.micr.dev/docs/reports).
|
|
70
108
|
|
|
71
|
-
##
|
|
109
|
+
## mcp
|
|
72
110
|
|
|
73
111
|
Complete setup and authentication, then configure your MCP client:
|
|
74
112
|
|
|
@@ -76,7 +114,7 @@ Complete setup and authentication, then configure your MCP client:
|
|
|
76
114
|
{
|
|
77
115
|
"mcpServers": {
|
|
78
116
|
"semrush": {
|
|
79
|
-
"command": "semrush",
|
|
117
|
+
"command": "semrush-cli",
|
|
80
118
|
"args": ["mcp"]
|
|
81
119
|
}
|
|
82
120
|
}
|
|
@@ -84,18 +122,20 @@ Complete setup and authentication, then configure your MCP client:
|
|
|
84
122
|
```
|
|
85
123
|
|
|
86
124
|
Use an absolute executable path if your client does not inherit the terminal's
|
|
87
|
-
PATH. `npx -y semrush-cli@0.
|
|
125
|
+
PATH. `npx -y semrush-cli@0.2.0 mcp` also works. MCP tool names use underscores,
|
|
88
126
|
for example `keyword_overview` and `domain_keywords`. Input schemas and reports
|
|
89
|
-
match the CLI.
|
|
90
|
-
|
|
127
|
+
match the CLI. The 54 read tools carry read-only annotations. The 8 write tools require
|
|
128
|
+
`confirm:true`, or accept `dry_run:true` to preview the request. Delete and leave
|
|
129
|
+
also carry destructive annotations. Login, arbitrary URLs and JavaScript
|
|
130
|
+
execution are not MCP tools.
|
|
91
131
|
|
|
92
|
-
##
|
|
132
|
+
## sessions
|
|
93
133
|
|
|
94
134
|
```sh
|
|
95
|
-
semrush auth status --check --json
|
|
96
|
-
semrush auth logout
|
|
97
|
-
semrush daemon stop
|
|
98
|
-
semrush daemon start
|
|
135
|
+
semrush-cli auth status --check --json
|
|
136
|
+
semrush-cli auth logout
|
|
137
|
+
semrush-cli daemon stop
|
|
138
|
+
semrush-cli daemon start
|
|
99
139
|
```
|
|
100
140
|
|
|
101
141
|
Galleton stores credentials under `~/.local/share/semrush-cli/galleton`. The
|
|
@@ -124,7 +164,7 @@ The setup/daemon commands manage only the default CLI-owned daemon. An external
|
|
|
124
164
|
daemon must support the documented static-provider extension. Never expose its
|
|
125
165
|
API token or credential directory.
|
|
126
166
|
|
|
127
|
-
##
|
|
167
|
+
## limits
|
|
128
168
|
|
|
129
169
|
Semrush account permissions, quotas and product access apply. A read-only report
|
|
130
170
|
can consume an allowance. Internal web endpoints may change. Provider caps remain
|
|
@@ -132,7 +172,7 @@ visible; there is no unlimited-export claim. Galleton encrypts at rest but its k
|
|
|
132
172
|
and vault share a private local directory. Other processes running as your OS user
|
|
133
173
|
are within the same trust boundary.
|
|
134
174
|
|
|
135
|
-
##
|
|
175
|
+
## development
|
|
136
176
|
|
|
137
177
|
```sh
|
|
138
178
|
npm ci
|
|
@@ -150,11 +190,11 @@ Chromium and Galleton with a local HTTP fixture, and exercise the built executab
|
|
|
150
190
|
They never require a Semrush account. See [verification](docs/verification.md) for
|
|
151
191
|
the separate live-account evidence.
|
|
152
192
|
|
|
153
|
-
##
|
|
193
|
+
## uninstall
|
|
154
194
|
|
|
155
195
|
```sh
|
|
156
|
-
semrush auth logout
|
|
157
|
-
semrush daemon stop
|
|
196
|
+
semrush-cli auth logout
|
|
197
|
+
semrush-cli daemon stop
|
|
158
198
|
npm uninstall -g semrush-cli
|
|
159
199
|
```
|
|
160
200
|
|
package/THIRD-PARTY-NOTICES.md
CHANGED
|
@@ -15,5 +15,7 @@ Microck/semrush-web-mcp, commit `764d102f`, under MIT.
|
|
|
15
15
|
Copyright (c) 2026 Microck. See the repository LICENSE.
|
|
16
16
|
|
|
17
17
|
All package dependencies retain their own licenses in their installed packages.
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
The modified logo derives from the official Semrush SVG icon in
|
|
19
|
+
https://brand.semrush.com/Semrush%20Logo.zip. Semrush owns the underlying mark.
|
|
20
|
+
This project is independent of Semrush. Its distribution contains no account
|
|
21
|
+
credentials.
|
package/assets/logo.png
CHANGED
|
Binary file
|
package/assets/logo.svg
CHANGED
|
@@ -1 +1,10 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 518 320" role="img" aria-labelledby="title">
|
|
2
|
+
<title id="title">semrush-cli</title>
|
|
3
|
+
<defs>
|
|
4
|
+
<mask id="terminal">
|
|
5
|
+
<rect width="518" height="320" fill="white"/>
|
|
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)"/>
|
|
10
|
+
</svg>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { n as SessionError, r as UsageError } from "./errors-
|
|
2
|
-
import { i as Sessions, n as origin } from "./session-layer-
|
|
1
|
+
import { n as SessionError, r as UsageError } from "./errors-CJuwKbEZ.js";
|
|
2
|
+
import { i as Sessions, n as origin } from "./session-layer-BIL6Umvb.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-CY1KdjJo.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-CYAtam3G.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-
|
|
1
|
+
{"version":3,"file":"auth-CYAtam3G.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 {
|
|
2
|
-
import { i as Sessions, n as origin } from "./session-layer-
|
|
1
|
+
import { a as projectRequest, n as SessionError, o as reportParameters, t as ReportError } from "./errors-CJuwKbEZ.js";
|
|
2
|
+
import { i as Sessions, n as origin } from "./session-layer-BIL6Umvb.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";
|
|
@@ -18,7 +18,7 @@ const json = JSONbig({
|
|
|
18
18
|
protoAction: "error",
|
|
19
19
|
constructorAction: "error"
|
|
20
20
|
});
|
|
21
|
-
const safeBrowserError = () => new ReportError({ message: "Semrush browser request failed. Run semrush doctor. If your session expired, sign in again with semrush auth login." });
|
|
21
|
+
const safeBrowserError = () => new ReportError({ message: "Semrush browser request failed. Run semrush-cli doctor. If your session expired, sign in again with semrush-cli auth login." });
|
|
22
22
|
const browserCall = (run) => Effect.tryPromise({
|
|
23
23
|
try: run,
|
|
24
24
|
catch: safeBrowserError
|
|
@@ -68,45 +68,57 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
68
68
|
});
|
|
69
69
|
return {
|
|
70
70
|
status: response.status,
|
|
71
|
-
text:
|
|
71
|
+
text: await response.text()
|
|
72
72
|
};
|
|
73
73
|
}));
|
|
74
74
|
const auth = yield* Effect.try({
|
|
75
75
|
try: () => readBootstrap(bootstrap.text),
|
|
76
|
-
catch: () => new SessionError({ message: "Semrush sign-in metadata unavailable. Run semrush auth login or import a signed-in browser." })
|
|
76
|
+
catch: () => new SessionError({ message: "Semrush sign-in metadata unavailable. Run semrush-cli auth login or import a signed-in browser." })
|
|
77
77
|
});
|
|
78
78
|
const semaphore = yield* Semaphore.make(1);
|
|
79
79
|
let spectrumToken = "";
|
|
80
|
-
const request = Effect.fn("Semrush.request")(function* (path, payload,
|
|
81
|
-
const wire = yield* browserCall(() => page.evaluate(async ({ path: path$1, encodedPayload,
|
|
80
|
+
const request = Effect.fn("Semrush.request")(function* (path, payload, method = "POST", extraHeaders = {}) {
|
|
81
|
+
const wire = yield* browserCall(() => page.evaluate(async ({ path: path$1, encodedPayload, method: method$1, extraHeaders: extraHeaders$1 }) => {
|
|
82
82
|
const payload$1 = JSON.parse(encodedPayload);
|
|
83
83
|
const query = new URLSearchParams();
|
|
84
|
-
|
|
84
|
+
const get = method$1 === "GET";
|
|
85
|
+
if (get) for (const [key, value] of Object.entries(payload$1)) for (const item of Array.isArray(value) ? value : [value]) query.append(key, String(item));
|
|
85
86
|
const options = {
|
|
86
|
-
method:
|
|
87
|
+
method: method$1,
|
|
87
88
|
credentials: "include",
|
|
88
89
|
headers: extraHeaders$1,
|
|
89
90
|
signal: AbortSignal.timeout(45e3)
|
|
90
91
|
};
|
|
91
|
-
if (!get$1) {
|
|
92
|
+
if (!get && method$1 !== "DELETE") {
|
|
92
93
|
options.headers = {
|
|
93
94
|
...extraHeaders$1,
|
|
94
95
|
"Content-Type": "application/json"
|
|
95
96
|
};
|
|
96
97
|
options.body = JSON.stringify(payload$1);
|
|
97
98
|
}
|
|
98
|
-
const response = await fetch(get
|
|
99
|
+
const response = await fetch(get && query.size > 0 ? path$1 + "?" + query : path$1, options);
|
|
99
100
|
return {
|
|
100
101
|
status: response.status,
|
|
101
|
-
text:
|
|
102
|
+
text: await response.text()
|
|
102
103
|
};
|
|
103
104
|
}, {
|
|
104
105
|
path,
|
|
105
106
|
encodedPayload: JSON.stringify(payload),
|
|
106
|
-
|
|
107
|
+
method,
|
|
107
108
|
extraHeaders
|
|
108
109
|
}));
|
|
109
|
-
if (wire.status
|
|
110
|
+
if (wire.status < 200 || wire.status >= 300) {
|
|
111
|
+
const detail = yield* Effect.try({
|
|
112
|
+
try: () => JSON.stringify(redact(parseWire(wire.text), [
|
|
113
|
+
auth.apiKey,
|
|
114
|
+
spectrumToken,
|
|
115
|
+
spectrumToken.replace(/^Bearer /, "")
|
|
116
|
+
])).slice(0, 500),
|
|
117
|
+
catch: safeBrowserError
|
|
118
|
+
}).pipe(Effect.option);
|
|
119
|
+
return yield* Effect.fail(new ReportError({ message: `Semrush request returned HTTP ${wire.status}.${Option.isSome(detail) ? " " + detail.value : " Check your session and account permissions."}` }));
|
|
120
|
+
}
|
|
121
|
+
if (wire.status === 204 && !wire.text) return null;
|
|
110
122
|
const body = yield* Effect.try({
|
|
111
123
|
try: () => parseWire(wire.text),
|
|
112
124
|
catch: () => new ReportError({ message: "Semrush returned a page or challenge instead of JSON. Sign in again or retry later." })
|
|
@@ -146,11 +158,19 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
146
158
|
signal: AbortSignal.timeout(15e3)
|
|
147
159
|
});
|
|
148
160
|
return response.ok && new URL(response.url).pathname === "/home/";
|
|
149
|
-
})))) return yield* Effect.fail(new SessionError({ message: "Semrush session expired. Run semrush auth login or import a signed-in browser." }));
|
|
150
|
-
const params = reportParameters(definition, query);
|
|
161
|
+
})))) return yield* Effect.fail(new SessionError({ message: "Semrush session expired. Run semrush-cli auth login or import a signed-in browser." }));
|
|
151
162
|
const family = definition.transport;
|
|
163
|
+
const planned = family === "project_web" ? projectRequest(definition, query) : void 0;
|
|
164
|
+
const params = planned?.body ?? reportParameters(definition, query);
|
|
152
165
|
let reportData;
|
|
153
|
-
if (
|
|
166
|
+
if (planned) {
|
|
167
|
+
if (name === "project_update" && query.domain !== void 0) {
|
|
168
|
+
const current = yield* request("/apis/v4-raw/folders/api/v0/folders/folder", { folder_id: query.folder_id ?? null }, "GET");
|
|
169
|
+
if (!objectValue(current) || !objectValue(current.folder)) return yield* Effect.fail(new ReportError({ message: "Project metadata unavailable. Inspect the project before adding a website." }));
|
|
170
|
+
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." }));
|
|
171
|
+
}
|
|
172
|
+
reportData = yield* request(planned.path, planned.body, planned.method);
|
|
173
|
+
} else if ([
|
|
154
174
|
"keyword",
|
|
155
175
|
"domain",
|
|
156
176
|
"direct",
|
|
@@ -173,14 +193,14 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
173
193
|
if (family === "backlink_overview") {
|
|
174
194
|
path += "overview/" + definition.method;
|
|
175
195
|
extra["x-request-id"] = randomUUID();
|
|
176
|
-
yield* request("/analytics/backlinks/webapi2/overview/init", params,
|
|
196
|
+
yield* request("/analytics/backlinks/webapi2/overview/init", params, "GET", extra);
|
|
177
197
|
}
|
|
178
198
|
} else if (path.startsWith("/siteaudit/")) params.key = auth.apiKey;
|
|
179
|
-
reportData = yield* request(path, params,
|
|
199
|
+
reportData = yield* request(path, params, [
|
|
180
200
|
"post",
|
|
181
201
|
"gap",
|
|
182
202
|
"projects"
|
|
183
|
-
].includes(family), extra);
|
|
203
|
+
].includes(family) ? "POST" : "GET", extra);
|
|
184
204
|
}
|
|
185
205
|
if (definition.total_method) {
|
|
186
206
|
const totalParams = name === "domain_position_changes" ? Object.fromEntries(Object.entries(params).filter(([key]) => key !== "display")) : params;
|
|
@@ -266,4 +286,4 @@ const captureCookies = Effect.fn("Semrush.captureCookies")(function* (context, c
|
|
|
266
286
|
|
|
267
287
|
//#endregion
|
|
268
288
|
export { semrushLive as n, readBootstrap as t };
|
|
269
|
-
//# sourceMappingURL=browser-layer-
|
|
289
|
+
//# sourceMappingURL=browser-layer-CY1KdjJo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-layer-CY1KdjJo.js","names":["browser","payload: Schema.JsonObject","method","payload","options: RequestInit","extraHeaders","path","reportData: Schema.Json | undefined","extra: Record<string, string>","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, projectRequest, 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']]);\nconst json = JSONbig({ storeAsString: true, protoAction: 'error', constructorAction: 'error' });\nconst safeBrowserError = () => new ReportError({ message: 'Semrush browser request failed. Run semrush-cli doctor. If your session expired, sign in again with semrush-cli 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-cli 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, method = 'POST', extraHeaders: Record<string, string> = {}) {\n const wire = yield* browserCall(() => page.evaluate(async ({ path, encodedPayload, method, extraHeaders }) => {\n const payload: Schema.JsonObject = 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') { options.headers = { ...extraHeaders, 'Content-Type': 'application/json' }; options.body = JSON.stringify(payload); }\n const response = await fetch(get && query.size > 0 ? path + '?' + 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 || ('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 body = yield* request(transportPaths.get(transport) ?? '/kwogw/v2/webapi', { id: 1, jsonrpc: '2.0', method, params: payload });\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 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-cli auth login or import a signed-in browser.' }));\n const family = definition.transport;\n const planned = family === 'project_web' ? projectRequest(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 reportData = yield* request(planned.path, planned.body, planned.method);\n } else if (['keyword', 'domain', 'direct', 'magic'].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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"}
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { i as Sessions, n as origin, r as sessionsLive, t as defaultStateDirectory } from "./session-layer-
|
|
2
|
+
import { c as version, i as previewMutation, n as SessionError, s as reports } from "./errors-CJuwKbEZ.js";
|
|
3
|
+
import { i as Sessions, n as origin, r as sessionsLive, t as defaultStateDirectory } from "./session-layer-BIL6Umvb.js";
|
|
4
4
|
import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
import { chmod, mkdir, open, writeFile } from "node:fs/promises";
|
|
@@ -13,7 +13,7 @@ import { fileURLToPath } from "node:url";
|
|
|
13
13
|
//#region src/daemon.ts
|
|
14
14
|
const exec = promisify(execFile);
|
|
15
15
|
const daemonPath = () => fileURLToPath(new URL(`./daemon/galleton-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`, import.meta.url));
|
|
16
|
-
const failed = () => new SessionError({ message: "Local daemon setup failed. Check semrush daemon start and the private daemon.log in the state directory." });
|
|
16
|
+
const failed = () => new SessionError({ message: "Local daemon setup failed. Check semrush-cli daemon start and the private daemon.log in the state directory." });
|
|
17
17
|
const io = (run) => Effect.tryPromise({
|
|
18
18
|
try: run,
|
|
19
19
|
catch: failed
|
|
@@ -123,8 +123,8 @@ const installBrowser = Effect.fn("Browser.install")(() => io(async () => {
|
|
|
123
123
|
//#endregion
|
|
124
124
|
//#region src/cli.ts
|
|
125
125
|
const interruption = new AbortController();
|
|
126
|
-
const cli = new Command().name("semrush").description("Semrush research through your signed-in account. CLI + MCP, powered by Effect and Galleton.").version(
|
|
127
|
-
cli.addHelpText("after", "\nExamples:\n semrush setup\n semrush auth login\n semrush domain-overview --domain openai.com --json\n semrush keyword-magic --keyword \"email marketing\" --page-size 20 --json\n semrush mcp\n\nDocumentation: https://semrush.micr.dev\n");
|
|
126
|
+
const cli = new Command().name("semrush-cli").description("Semrush research through your signed-in account. CLI + MCP, powered by Effect and Galleton.").version(version).exitOverride().showHelpAfterError().option("--json", "Emit compact JSON").option("--no-input", "Never prompt for terminal input").option("--no-color", "Disable terminal colors");
|
|
127
|
+
cli.addHelpText("after", "\nExamples:\n semrush-cli setup\n semrush-cli auth login\n semrush-cli domain-overview --domain openai.com --json\n semrush-cli keyword-magic --keyword \"email marketing\" --page-size 20 --json\n semrush-cli mcp\n\nDocumentation: https://semrush.micr.dev\n");
|
|
128
128
|
function output(value, command) {
|
|
129
129
|
process.stdout.write(JSON.stringify(value, null, command.optsWithGlobals().json ? void 0 : 2) + "\n");
|
|
130
130
|
}
|
|
@@ -132,16 +132,18 @@ async function execute(program, command) {
|
|
|
132
132
|
await Effect.runPromise(Effect.scoped(program).pipe(Effect.match({
|
|
133
133
|
onSuccess: (value) => output(value, command),
|
|
134
134
|
onFailure: (error) => {
|
|
135
|
-
process.stderr.write(`semrush: ${error.message}\n`);
|
|
135
|
+
process.stderr.write(`semrush-cli: ${error.message}\n`);
|
|
136
136
|
process.exitCode = "name" in error && error.name === "UsageError" ? 2 : 1;
|
|
137
137
|
}
|
|
138
138
|
})), { signal: interruption.signal });
|
|
139
139
|
}
|
|
140
|
-
cli.command("reports").description("List
|
|
140
|
+
cli.command("reports").description("List research reports and project operations").action((_options, command) => {
|
|
141
141
|
output(Object.entries(reports).map(([name, report]) => ({
|
|
142
142
|
command: name.replaceAll("_", "-"),
|
|
143
143
|
tool: name,
|
|
144
144
|
description: report.description,
|
|
145
|
+
read_only: !report.mutation,
|
|
146
|
+
destructive: report.destructive,
|
|
145
147
|
schema: report.schema
|
|
146
148
|
})), command);
|
|
147
149
|
});
|
|
@@ -173,7 +175,7 @@ for (const [name, definition] of Object.entries(reports)) {
|
|
|
173
175
|
report.addOption(option);
|
|
174
176
|
}
|
|
175
177
|
report.action(async (_options, command) => {
|
|
176
|
-
const { parseReport } = await import("./validation-
|
|
178
|
+
const { parseReport } = await import("./validation-DBzNMr6w.js");
|
|
177
179
|
const options = command.opts();
|
|
178
180
|
const input = Object.fromEntries(Object.keys(schema.properties).map((key) => [key, options[key.replace(/_([a-z])/g, (_match, char) => char.toUpperCase())]]).filter(([, value]) => value !== void 0));
|
|
179
181
|
const parsed = await Effect.runPromise(parseReport(name, input).pipe(Effect.match({
|
|
@@ -187,11 +189,16 @@ for (const [name, definition] of Object.entries(reports)) {
|
|
|
187
189
|
})
|
|
188
190
|
})));
|
|
189
191
|
if (!parsed.ok) {
|
|
190
|
-
process.stderr.write(`semrush: ${parsed.error.message}\n`);
|
|
192
|
+
process.stderr.write(`semrush-cli: ${parsed.error.message}\n`);
|
|
191
193
|
process.exitCode = 2;
|
|
192
194
|
return;
|
|
193
195
|
}
|
|
194
|
-
const
|
|
196
|
+
const preview = previewMutation(parsed.value.name, parsed.value.definition, parsed.value.query);
|
|
197
|
+
if (preview) {
|
|
198
|
+
output(preview, command);
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
const { appLayer } = await import("./mcp-CSgmSxRc.js");
|
|
195
202
|
const { Semrush } = await import("./browser-service-CL8YPaIT.js");
|
|
196
203
|
await execute(Effect.gen(function* () {
|
|
197
204
|
return yield* (yield* Semrush).report(parsed.value);
|
|
@@ -203,21 +210,21 @@ cli.command("setup").description("Install Chromium and initialize the private Ga
|
|
|
203
210
|
yield* installBrowser();
|
|
204
211
|
return {
|
|
205
212
|
...yield* startDaemon(),
|
|
206
|
-
next: "semrush auth login"
|
|
213
|
+
next: "semrush-cli auth login"
|
|
207
214
|
};
|
|
208
215
|
}), command));
|
|
209
216
|
const daemon = cli.command("daemon").description("Control the CLI-owned Galleton daemon");
|
|
210
217
|
daemon.command("start").description("Start the private daemon").action((_options, command) => execute(startDaemon(), command));
|
|
211
218
|
daemon.command("stop").description("Gracefully stop the private daemon").action((_options, command) => execute(stopDaemon(), command));
|
|
212
219
|
const auth = cli.command("auth").description("Store and inspect your Semrush session in Galleton");
|
|
213
|
-
auth.command("import").description("Import a signed-in local Chromium context").requiredOption("--cdp-url <url>", "Loopback CDP URL, e.g. http://127.0.0.1:9234").action(async (options, command) => execute((await import("./auth-
|
|
214
|
-
auth.command("login").description("Sign in through an isolated visible Chromium window").action(async (_options, command) => execute((await import("./auth-
|
|
220
|
+
auth.command("import").description("Import a signed-in local Chromium context").requiredOption("--cdp-url <url>", "Loopback CDP URL, e.g. http://127.0.0.1:9234").action(async (options, command) => execute((await import("./auth-CYAtam3G.js")).importBrowser(options.cdpUrl).pipe(Effect.provide(sessionsLive)), command));
|
|
221
|
+
auth.command("login").description("Sign in through an isolated visible Chromium window").action(async (_options, command) => execute((await import("./auth-CYAtam3G.js")).login().pipe(Effect.provide(sessionsLive)), command));
|
|
215
222
|
auth.command("status").description("Show session metadata without credentials").option("--check", "Verify the stored session against Semrush").action((options, command) => execute(Effect.gen(function* () {
|
|
216
223
|
const status = yield* (yield* Sessions).status();
|
|
217
224
|
if (options.check) {
|
|
218
|
-
const { appLayer } = yield* Effect.promise(() => import("./mcp-
|
|
225
|
+
const { appLayer } = yield* Effect.promise(() => import("./mcp-CSgmSxRc.js"));
|
|
219
226
|
const { Semrush } = yield* Effect.promise(() => import("./browser-service-CL8YPaIT.js"));
|
|
220
|
-
const { parseReport } = yield* Effect.promise(() => import("./validation-
|
|
227
|
+
const { parseReport } = yield* Effect.promise(() => import("./validation-DBzNMr6w.js"));
|
|
221
228
|
yield* Effect.gen(function* () {
|
|
222
229
|
return yield* (yield* Semrush).report(yield* parseReport("account_status", {}));
|
|
223
230
|
}).pipe(Effect.provide(appLayer));
|
|
@@ -237,9 +244,9 @@ auth.command("logout").description("Forget this CLI session from Galleton").acti
|
|
|
237
244
|
};
|
|
238
245
|
}).pipe(Effect.provide(sessionsLive)), command));
|
|
239
246
|
cli.command("doctor").description("Check Galleton, browser and authenticated report access").action(async (_options, command) => {
|
|
240
|
-
const { appLayer } = await import("./mcp-
|
|
247
|
+
const { appLayer } = await import("./mcp-CSgmSxRc.js");
|
|
241
248
|
const { Semrush } = await import("./browser-service-CL8YPaIT.js");
|
|
242
|
-
const { parseReport } = await import("./validation-
|
|
249
|
+
const { parseReport } = await import("./validation-DBzNMr6w.js");
|
|
243
250
|
await execute(Effect.gen(function* () {
|
|
244
251
|
yield* (yield* Semrush).report(yield* parseReport("account_status", {}));
|
|
245
252
|
return {
|
|
@@ -250,7 +257,7 @@ cli.command("doctor").description("Check Galleton, browser and authenticated rep
|
|
|
250
257
|
};
|
|
251
258
|
}).pipe(Effect.provide(appLayer)), command);
|
|
252
259
|
});
|
|
253
|
-
cli.command("mcp").description("Run the
|
|
260
|
+
cli.command("mcp").description("Run the MCP server over stdio").action(async () => (await import("./mcp-CSgmSxRc.js")).serveMcp());
|
|
254
261
|
process.once("SIGINT", () => {
|
|
255
262
|
if (process.argv.includes("mcp")) return;
|
|
256
263
|
process.exitCode = 130;
|
|
@@ -262,7 +269,7 @@ try {
|
|
|
262
269
|
if (interruption.signal.aborted) process.exitCode = 130;
|
|
263
270
|
else if (error instanceof CommanderError) process.exitCode = error.exitCode === 0 ? 0 : 2;
|
|
264
271
|
else {
|
|
265
|
-
process.stderr.write("semrush: Unexpected failure. Run semrush doctor and report the failing command at https://github.com/Microck/semrush-cli/issues.\n");
|
|
272
|
+
process.stderr.write("semrush-cli: Unexpected failure. Run semrush-cli doctor and report the failing command at https://github.com/Microck/semrush-cli/issues.\n");
|
|
266
273
|
process.exitCode = 1;
|
|
267
274
|
}
|
|
268
275
|
}
|