semrush-cli 0.1.1 → 0.3.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 +74 -19
- package/THIRD-PARTY-NOTICES.md +4 -2
- package/assets/logo.png +0 -0
- package/assets/logo.svg +10 -1
- package/dist/{auth-BcUPx0Nk.js → auth-BZhlPkLq.js} +4 -4
- package/dist/{auth-BcUPx0Nk.js.map → auth-BZhlPkLq.js.map} +1 -1
- package/dist/{browser-layer-D6mEyv9Y.js → browser-layer-BJ4eQAUz.js} +57 -26
- package/dist/browser-layer-BJ4eQAUz.js.map +1 -0
- package/dist/cli.js +19 -12
- package/dist/cli.js.map +1 -1
- package/dist/{errors-BhvanypE.js → errors-CU9T4acd.js} +1774 -164
- package/dist/errors-CU9T4acd.js.map +1 -0
- package/dist/{mcp-Cuzenjra.js → mcp-BKmnk8Wq.js} +13 -7
- package/dist/mcp-BKmnk8Wq.js.map +1 -0
- package/dist/{session-layer-BUU0ZG9M.js → session-layer-BDgTji_P.js} +2 -2
- package/dist/{session-layer-BUU0ZG9M.js.map → session-layer-BDgTji_P.js.map} +1 -1
- package/dist/validation-D0km8JI3.js +4 -0
- package/dist/{validation-C_lcBSzX.js → validation-Qgx4O9Cz.js} +5 -3
- package/dist/validation-Qgx4O9Cz.js.map +1 -0
- package/package.json +4 -3
- package/dist/browser-layer-D6mEyv9Y.js.map +0 -1
- package/dist/errors-BhvanypE.js.map +0 -1
- package/dist/mcp-Cuzenjra.js.map +0 -1
- package/dist/validation-BH5mq69L.js +0 -4
- package/dist/validation-C_lcBSzX.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,16 +1,31 @@
|
|
|
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
|
-
|
|
12
|
+
---
|
|
13
|
+
|
|
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
|
|
7
21
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
No Semrush developer API key, Python runtime or CAPTCHA service required.
|
|
22
|
+
81 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.
|
|
12
25
|
|
|
13
|
-
|
|
26
|
+
The executable is `semrush-cli`. It can coexist with other tools named `semrush`.
|
|
27
|
+
|
|
28
|
+
## quickstart
|
|
14
29
|
|
|
15
30
|
Requires Node.js 22+. Includes Galleton binaries for Linux x64/arm64,
|
|
16
31
|
macOS x64/arm64 and Windows x64.
|
|
@@ -39,7 +54,43 @@ supports the browser managed by [semrush-web-mcp](https://github.com/Microck/sem
|
|
|
39
54
|
Import does not stop or change that browser. Keep CDP private; forward it over
|
|
40
55
|
SSH when necessary.
|
|
41
56
|
|
|
42
|
-
##
|
|
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
|
+
## keyword lists
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
semrush-cli keyword-lists --json
|
|
81
|
+
semrush-cli keyword-list-create --name 'research' --confirm --json
|
|
82
|
+
semrush-cli keyword-list-import --basket-id 456 --owner-id 123 --keywords '["email marketing"]' --confirm
|
|
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
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Builder list IDs and owner IDs come from `keyword-lists`. Regular list creation
|
|
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).
|
|
92
|
+
|
|
93
|
+
## commands
|
|
43
94
|
|
|
44
95
|
```sh
|
|
45
96
|
semrush-cli keyword-overview --keyword 'email marketing' --json
|
|
@@ -62,7 +113,9 @@ semrush-cli domain-keywords --help
|
|
|
62
113
|
| Gaps | Keyword overlaps and gaps, backlink prospects and profile comparisons |
|
|
63
114
|
| Backlinks | Links, referring domains, anchors, indexed pages, outbound domains, history and authority |
|
|
64
115
|
| AI visibility | Visibility, mentions, citation sources and country distribution |
|
|
65
|
-
|
|
|
116
|
+
| Keyword lists | Create, read, rename/delete, import, metrics, tags, deletion/undo, clustering and structured strategy generation |
|
|
117
|
+
| Projects | Create, inspect, rename, delete, leave, tags, pinning, toolkit status, dashboard metrics and explicit SEO setup |
|
|
118
|
+
| Account | Limits, permissions, databases, snapshots, folders, saved lists and audit campaigns |
|
|
66
119
|
|
|
67
120
|
All successful reports use `{authenticated: true, report, data}`. Domain ranking
|
|
68
121
|
and page reports include provider totals. Large integers remain exact as decimal
|
|
@@ -70,7 +123,7 @@ strings. Errors go to stderr with a nonzero exit. `--json` works before or after
|
|
|
70
123
|
the command. Objects and arrays use JSON flags; booleans support explicit `false`.
|
|
71
124
|
See the [full command reference](https://semrush.micr.dev/docs/reports).
|
|
72
125
|
|
|
73
|
-
##
|
|
126
|
+
## mcp
|
|
74
127
|
|
|
75
128
|
Complete setup and authentication, then configure your MCP client:
|
|
76
129
|
|
|
@@ -86,12 +139,14 @@ Complete setup and authentication, then configure your MCP client:
|
|
|
86
139
|
```
|
|
87
140
|
|
|
88
141
|
Use an absolute executable path if your client does not inherit the terminal's
|
|
89
|
-
PATH. `npx -y semrush-cli@0.
|
|
142
|
+
PATH. `npx -y semrush-cli@0.3.0 mcp` also works. MCP tool names use underscores,
|
|
90
143
|
for example `keyword_overview` and `domain_keywords`. Input schemas and reports
|
|
91
|
-
match the CLI.
|
|
92
|
-
|
|
144
|
+
match the CLI. The 62 read tools carry read-only annotations. The 19 write tools require
|
|
145
|
+
`confirm:true`, or accept `dry_run:true` to preview the request. Delete and leave
|
|
146
|
+
also carry destructive annotations. Login, arbitrary URLs and JavaScript
|
|
147
|
+
execution are not MCP tools.
|
|
93
148
|
|
|
94
|
-
##
|
|
149
|
+
## sessions
|
|
95
150
|
|
|
96
151
|
```sh
|
|
97
152
|
semrush-cli auth status --check --json
|
|
@@ -126,7 +181,7 @@ The setup/daemon commands manage only the default CLI-owned daemon. An external
|
|
|
126
181
|
daemon must support the documented static-provider extension. Never expose its
|
|
127
182
|
API token or credential directory.
|
|
128
183
|
|
|
129
|
-
##
|
|
184
|
+
## limits
|
|
130
185
|
|
|
131
186
|
Semrush account permissions, quotas and product access apply. A read-only report
|
|
132
187
|
can consume an allowance. Internal web endpoints may change. Provider caps remain
|
|
@@ -134,7 +189,7 @@ visible; there is no unlimited-export claim. Galleton encrypts at rest but its k
|
|
|
134
189
|
and vault share a private local directory. Other processes running as your OS user
|
|
135
190
|
are within the same trust boundary.
|
|
136
191
|
|
|
137
|
-
##
|
|
192
|
+
## development
|
|
138
193
|
|
|
139
194
|
```sh
|
|
140
195
|
npm ci
|
|
@@ -152,7 +207,7 @@ Chromium and Galleton with a local HTTP fixture, and exercise the built executab
|
|
|
152
207
|
They never require a Semrush account. See [verification](docs/verification.md) for
|
|
153
208
|
the separate live-account evidence.
|
|
154
209
|
|
|
155
|
-
##
|
|
210
|
+
## uninstall
|
|
156
211
|
|
|
157
212
|
```sh
|
|
158
213
|
semrush-cli auth logout
|
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-CU9T4acd.js";
|
|
2
|
+
import { i as Sessions, n as origin } from "./session-layer-BDgTji_P.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-BJ4eQAUz.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-BZhlPkLq.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-
|
|
1
|
+
{"version":3,"file":"auth-BZhlPkLq.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 reportParameters, l as webRequest, n as SessionError, s as rpcBody, t as ReportError } from "./errors-CU9T4acd.js";
|
|
2
|
+
import { i as Sessions, n as origin } from "./session-layer-BDgTji_P.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";
|
|
@@ -11,7 +11,8 @@ const transportPaths = new Map([
|
|
|
11
11
|
["domain", "/dpa/rpc"],
|
|
12
12
|
["direct", "/dpa/rpc"],
|
|
13
13
|
["magic", "/kmtgw/v2/webapi"],
|
|
14
|
-
["keyword", "/kwogw/v2/webapi"]
|
|
14
|
+
["keyword", "/kwogw/v2/webapi"],
|
|
15
|
+
["kwm", "/kwm/rpc"]
|
|
15
16
|
]);
|
|
16
17
|
const json = JSONbig({
|
|
17
18
|
storeAsString: true,
|
|
@@ -68,7 +69,7 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
68
69
|
});
|
|
69
70
|
return {
|
|
70
71
|
status: response.status,
|
|
71
|
-
text:
|
|
72
|
+
text: await response.text()
|
|
72
73
|
};
|
|
73
74
|
}));
|
|
74
75
|
const auth = yield* Effect.try({
|
|
@@ -77,36 +78,48 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
77
78
|
});
|
|
78
79
|
const semaphore = yield* Semaphore.make(1);
|
|
79
80
|
let spectrumToken = "";
|
|
80
|
-
const request = Effect.fn("Semrush.request")(function* (path, payload,
|
|
81
|
-
const wire = yield* browserCall(() => page.evaluate(async ({ path: path$1, encodedPayload,
|
|
81
|
+
const request = Effect.fn("Semrush.request")(function* (path, payload, method = "POST", extraHeaders = {}) {
|
|
82
|
+
const wire = yield* browserCall(() => page.evaluate(async ({ path: path$1, encodedPayload, method: method$1, extraHeaders: extraHeaders$1 }) => {
|
|
82
83
|
const payload$1 = JSON.parse(encodedPayload);
|
|
83
84
|
const query = new URLSearchParams();
|
|
84
|
-
|
|
85
|
+
const get = method$1 === "GET";
|
|
86
|
+
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
87
|
const options = {
|
|
86
|
-
method:
|
|
88
|
+
method: method$1,
|
|
87
89
|
credentials: "include",
|
|
88
90
|
headers: extraHeaders$1,
|
|
89
91
|
signal: AbortSignal.timeout(45e3)
|
|
90
92
|
};
|
|
91
|
-
if (!get$1) {
|
|
93
|
+
if (!get && method$1 !== "DELETE") {
|
|
92
94
|
options.headers = {
|
|
93
95
|
...extraHeaders$1,
|
|
94
96
|
"Content-Type": "application/json"
|
|
95
97
|
};
|
|
96
98
|
options.body = JSON.stringify(payload$1);
|
|
97
99
|
}
|
|
98
|
-
const response = await fetch(get
|
|
100
|
+
const response = await fetch(get && query.size > 0 ? path$1 + "?" + query : path$1, options);
|
|
99
101
|
return {
|
|
100
102
|
status: response.status,
|
|
101
|
-
text:
|
|
103
|
+
text: await response.text()
|
|
102
104
|
};
|
|
103
105
|
}, {
|
|
104
106
|
path,
|
|
105
107
|
encodedPayload: JSON.stringify(payload),
|
|
106
|
-
|
|
108
|
+
method,
|
|
107
109
|
extraHeaders
|
|
108
110
|
}));
|
|
109
|
-
if (wire.status
|
|
111
|
+
if (wire.status < 200 || wire.status >= 300) {
|
|
112
|
+
const detail = yield* Effect.try({
|
|
113
|
+
try: () => JSON.stringify(redact(parseWire(wire.text), [
|
|
114
|
+
auth.apiKey,
|
|
115
|
+
spectrumToken,
|
|
116
|
+
spectrumToken.replace(/^Bearer /, "")
|
|
117
|
+
])).slice(0, 500),
|
|
118
|
+
catch: safeBrowserError
|
|
119
|
+
}).pipe(Effect.option);
|
|
120
|
+
return yield* Effect.fail(new ReportError({ message: `Semrush request returned HTTP ${wire.status}.${Option.isSome(detail) ? " " + detail.value : " Check your session and account permissions."}` }));
|
|
121
|
+
}
|
|
122
|
+
if (wire.status === 204 && !wire.text) return null;
|
|
110
123
|
const body = yield* Effect.try({
|
|
111
124
|
try: () => parseWire(wire.text),
|
|
112
125
|
catch: () => new ReportError({ message: "Semrush returned a page or challenge instead of JSON. Sign in again or retry later." })
|
|
@@ -128,12 +141,21 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
128
141
|
...params,
|
|
129
142
|
...auth
|
|
130
143
|
} : params;
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
const rpcParams = transport === "kwm" ? {
|
|
145
|
+
...payload,
|
|
146
|
+
...method.startsWith("user.") ? {
|
|
147
|
+
api_key: auth.apiKey,
|
|
148
|
+
user_id: auth.userId
|
|
149
|
+
} : {
|
|
150
|
+
APIKey: auth.apiKey,
|
|
151
|
+
UserID: auth.userId
|
|
152
|
+
}
|
|
153
|
+
} : payload;
|
|
154
|
+
const extra = transport === "kwm" ? {
|
|
155
|
+
"api-key": auth.apiKey,
|
|
156
|
+
"user-id": String(auth.userId)
|
|
157
|
+
} : {};
|
|
158
|
+
const body = yield* request(transportPaths.get(transport) ?? "/kwogw/v2/webapi", rpcBody(method, rpcParams), "POST", extra);
|
|
137
159
|
if (!objectValue(body) || !("result" in body)) return yield* Effect.fail(new ReportError({ message: `Semrush ${method}: missing report result.` }));
|
|
138
160
|
return body.result ?? null;
|
|
139
161
|
});
|
|
@@ -147,14 +169,23 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
147
169
|
});
|
|
148
170
|
return response.ok && new URL(response.url).pathname === "/home/";
|
|
149
171
|
})))) return yield* Effect.fail(new SessionError({ message: "Semrush session expired. Run semrush-cli auth login or import a signed-in browser." }));
|
|
150
|
-
const params = reportParameters(definition, query);
|
|
151
172
|
const family = definition.transport;
|
|
173
|
+
const planned = family === "project_web" ? webRequest(definition, query) : void 0;
|
|
174
|
+
const params = planned?.body ?? reportParameters(definition, query);
|
|
152
175
|
let reportData;
|
|
153
|
-
if (
|
|
176
|
+
if (planned) {
|
|
177
|
+
if (name === "project_update" && query.domain !== void 0) {
|
|
178
|
+
const current = yield* request("/apis/v4-raw/folders/api/v0/folders/folder", { folder_id: query.folder_id ?? null }, "GET");
|
|
179
|
+
if (!objectValue(current) || !objectValue(current.folder)) return yield* Effect.fail(new ReportError({ message: "Project metadata unavailable. Inspect the project before adding a website." }));
|
|
180
|
+
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." }));
|
|
181
|
+
}
|
|
182
|
+
reportData = yield* request(planned.path, planned.body, planned.method);
|
|
183
|
+
} else if ([
|
|
154
184
|
"keyword",
|
|
155
185
|
"domain",
|
|
156
186
|
"direct",
|
|
157
|
-
"magic"
|
|
187
|
+
"magic",
|
|
188
|
+
"kwm"
|
|
158
189
|
].includes(family)) reportData = yield* rpc(definition.method, params, family, definition.web_report);
|
|
159
190
|
else {
|
|
160
191
|
const extra = {};
|
|
@@ -173,14 +204,14 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
173
204
|
if (family === "backlink_overview") {
|
|
174
205
|
path += "overview/" + definition.method;
|
|
175
206
|
extra["x-request-id"] = randomUUID();
|
|
176
|
-
yield* request("/analytics/backlinks/webapi2/overview/init", params,
|
|
207
|
+
yield* request("/analytics/backlinks/webapi2/overview/init", params, "GET", extra);
|
|
177
208
|
}
|
|
178
209
|
} else if (path.startsWith("/siteaudit/")) params.key = auth.apiKey;
|
|
179
|
-
reportData = yield* request(path, params,
|
|
210
|
+
reportData = yield* request(path, params, [
|
|
180
211
|
"post",
|
|
181
212
|
"gap",
|
|
182
213
|
"projects"
|
|
183
|
-
].includes(family), extra);
|
|
214
|
+
].includes(family) ? "POST" : "GET", extra);
|
|
184
215
|
}
|
|
185
216
|
if (definition.total_method) {
|
|
186
217
|
const totalParams = name === "domain_position_changes" ? Object.fromEntries(Object.entries(params).filter(([key]) => key !== "display")) : params;
|
|
@@ -266,4 +297,4 @@ const captureCookies = Effect.fn("Semrush.captureCookies")(function* (context, c
|
|
|
266
297
|
|
|
267
298
|
//#endregion
|
|
268
299
|
export { semrushLive as n, readBootstrap as t };
|
|
269
|
-
//# sourceMappingURL=browser-layer-
|
|
300
|
+
//# sourceMappingURL=browser-layer-BJ4eQAUz.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-layer-BJ4eQAUz.js","names":["browser","payload: Schema.JsonObject","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-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 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 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' ? 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 reportData = yield* request(planned.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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"}
|
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, o as reports } from "./errors-CU9T4acd.js";
|
|
3
|
+
import { i as Sessions, n as origin, r as sessionsLive, t as defaultStateDirectory } from "./session-layer-BDgTji_P.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";
|
|
@@ -137,11 +137,13 @@ async function execute(program, command) {
|
|
|
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-D0km8JI3.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({
|
|
@@ -191,7 +193,12 @@ for (const [name, definition] of Object.entries(reports)) {
|
|
|
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-BKmnk8Wq.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);
|
|
@@ -210,14 +217,14 @@ const daemon = cli.command("daemon").description("Control the CLI-owned Galleton
|
|
|
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-BZhlPkLq.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-BZhlPkLq.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-BKmnk8Wq.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-D0km8JI3.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-BKmnk8Wq.js");
|
|
241
248
|
const { Semrush } = await import("./browser-service-CL8YPaIT.js");
|
|
242
|
-
const { parseReport } = await import("./validation-
|
|
249
|
+
const { parseReport } = await import("./validation-D0km8JI3.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-BKmnk8Wq.js")).serveMcp());
|
|
254
261
|
process.once("SIGINT", () => {
|
|
255
262
|
if (process.argv.includes("mcp")) return;
|
|
256
263
|
process.exitCode = 130;
|