semrush-cli 0.1.1 → 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 +57 -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-CYAtam3G.js} +4 -4
- package/dist/{auth-BcUPx0Nk.js.map → auth-CYAtam3G.js.map} +1 -1
- package/dist/{browser-layer-D6mEyv9Y.js → browser-layer-CY1KdjJo.js} +38 -18
- package/dist/browser-layer-CY1KdjJo.js.map +1 -0
- package/dist/cli.js +19 -12
- package/dist/cli.js.map +1 -1
- package/dist/{errors-BhvanypE.js → errors-CJuwKbEZ.js} +677 -50
- package/dist/errors-CJuwKbEZ.js.map +1 -0
- package/dist/{mcp-Cuzenjra.js → mcp-CSgmSxRc.js} +13 -7
- package/dist/mcp-CSgmSxRc.js.map +1 -0
- package/dist/{session-layer-BUU0ZG9M.js → session-layer-BIL6Umvb.js} +2 -2
- package/dist/{session-layer-BUU0ZG9M.js.map → session-layer-BIL6Umvb.js.map} +1 -1
- package/dist/{validation-C_lcBSzX.js → validation-BCtts-Al.js} +5 -3
- package/dist/validation-BCtts-Al.js.map +1 -0
- package/dist/validation-DBzNMr6w.js +4 -0
- package/package.json +1 -1
- 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)
|
|
7
19
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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`.
|
|
12
27
|
|
|
13
|
-
##
|
|
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,27 @@ 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
|
+
## commands
|
|
43
78
|
|
|
44
79
|
```sh
|
|
45
80
|
semrush-cli keyword-overview --keyword 'email marketing' --json
|
|
@@ -62,7 +97,8 @@ semrush-cli domain-keywords --help
|
|
|
62
97
|
| Gaps | Keyword overlaps and gaps, backlink prospects and profile comparisons |
|
|
63
98
|
| Backlinks | Links, referring domains, anchors, indexed pages, outbound domains, history and authority |
|
|
64
99
|
| AI visibility | Visibility, mentions, citation sources and country distribution |
|
|
65
|
-
|
|
|
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 |
|
|
66
102
|
|
|
67
103
|
All successful reports use `{authenticated: true, report, data}`. Domain ranking
|
|
68
104
|
and page reports include provider totals. Large integers remain exact as decimal
|
|
@@ -70,7 +106,7 @@ strings. Errors go to stderr with a nonzero exit. `--json` works before or after
|
|
|
70
106
|
the command. Objects and arrays use JSON flags; booleans support explicit `false`.
|
|
71
107
|
See the [full command reference](https://semrush.micr.dev/docs/reports).
|
|
72
108
|
|
|
73
|
-
##
|
|
109
|
+
## mcp
|
|
74
110
|
|
|
75
111
|
Complete setup and authentication, then configure your MCP client:
|
|
76
112
|
|
|
@@ -86,12 +122,14 @@ Complete setup and authentication, then configure your MCP client:
|
|
|
86
122
|
```
|
|
87
123
|
|
|
88
124
|
Use an absolute executable path if your client does not inherit the terminal's
|
|
89
|
-
PATH. `npx -y semrush-cli@0.
|
|
125
|
+
PATH. `npx -y semrush-cli@0.2.0 mcp` also works. MCP tool names use underscores,
|
|
90
126
|
for example `keyword_overview` and `domain_keywords`. Input schemas and reports
|
|
91
|
-
match the CLI.
|
|
92
|
-
|
|
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.
|
|
93
131
|
|
|
94
|
-
##
|
|
132
|
+
## sessions
|
|
95
133
|
|
|
96
134
|
```sh
|
|
97
135
|
semrush-cli auth status --check --json
|
|
@@ -126,7 +164,7 @@ The setup/daemon commands manage only the default CLI-owned daemon. An external
|
|
|
126
164
|
daemon must support the documented static-provider extension. Never expose its
|
|
127
165
|
API token or credential directory.
|
|
128
166
|
|
|
129
|
-
##
|
|
167
|
+
## limits
|
|
130
168
|
|
|
131
169
|
Semrush account permissions, quotas and product access apply. A read-only report
|
|
132
170
|
can consume an allowance. Internal web endpoints may change. Provider caps remain
|
|
@@ -134,7 +172,7 @@ visible; there is no unlimited-export claim. Galleton encrypts at rest but its k
|
|
|
134
172
|
and vault share a private local directory. Other processes running as your OS user
|
|
135
173
|
are within the same trust boundary.
|
|
136
174
|
|
|
137
|
-
##
|
|
175
|
+
## development
|
|
138
176
|
|
|
139
177
|
```sh
|
|
140
178
|
npm ci
|
|
@@ -152,7 +190,7 @@ Chromium and Galleton with a local HTTP fixture, and exercise the built executab
|
|
|
152
190
|
They never require a Semrush account. See [verification](docs/verification.md) for
|
|
153
191
|
the separate live-account evidence.
|
|
154
192
|
|
|
155
|
-
##
|
|
193
|
+
## uninstall
|
|
156
194
|
|
|
157
195
|
```sh
|
|
158
196
|
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-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":";;;;;;;;AAOA,MAAM,mBAAmB,IAAI,aAAa,EAAE,SAAS,6HAA6H,CAAC;AACnL,MAAM,YAAe,QAA0B,OAAO,WAAW;CAAE,KAAK;CAAK,OAAO;CAAY,CAAC;AAEjG,MAAM,cAAc,OAAO,GAAG,mBAAmB,CAAC,WAAW,SAAyB;CACpF,MAAM,WAAW,OAAO;CACxB,MAAM,OAAO,OAAO,OAAO,eAAe,eAAe,QAAQ,SAAS,CAAC,GAAE,WAAQ,OAAO,cAAcA,OAAK,OAAO,CAAC,CAAC;AACxH,QAAO,eAAe,KAAK,KAAK,SAAS,eAAe;EAAE,WAAW;EAAoB,SAAS;EAAO,CAAC,CAAC;CAC3G,MAAM,OAAO,OAAO,eAAe,KAAK,SAAS,YAAY;EAC3D,MAAM,WAAW,MAAM,MAAM,8BAA8B;GAAE,aAAa;GAAW,QAAQ,YAAY,QAAQ,KAAM;GAAE,CAAC;AAC1H,SAAO,SAAS,KAAK,SAAS,MAAM,GAAG;GACvC,CAAC;AACH,QAAO,OAAO,IAAI;EAAE,WAAW,cAAc,KAAK;EAAE,OAAO;EAAY,CAAC;CACxE,MAAM,UAAU,OAAO,eAAe,QAAQ,QAAQ,OAAO,CAAC;AAC9D,KAAI,CAAC,QAAQ,OAAQ,QAAO,OAAO,OAAO,KAAK,YAAY,CAAC;AAE5D,QAAO,OAAO,SAAS,KAAK;EAAE,UAAU;EAAW,eAAe;EAAQ,eAAe,QAAQ,KAAI,WAAU,GAAG,OAAO,KAAK,GAAG,OAAO,QAAQ,CAAC,KAAK,KAAK;EAAE,CAAC;EAC9J;AAEF,MAAa,gBAAgB,OAAO,GAAG,cAAc,CAAC,WAAW,KAAa;CAC5E,MAAM,QAAQ,OAAO,OAAO,IAAI;EAAE,WAAW;GAC3C,MAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,OAAI,OAAO,aAAa,WAAW,CAAC,CAAC,aAAa,QAAQ,CAAC,SAAS,OAAO,SAAS,IAAI,OAAO,YAAY,OAAO,SAAU,OAAM,IAAI,MAAM,eAAe;AAC3J,UAAO,OAAO;;EACb,aAAa,IAAI,WAAW,EAAE,SAAS,oFAAoF,CAAC;EAAE,CAAC;CAClI,MAAM,UAAU,OAAO,eAAe,SAAS,eAAe,OAAO,EAAE,SAAS,KAAO,CAAC,CAAC;AAEzF,QAAO,OAAO,mBAAmB,OAAO,cAAc,QAAQ,OAAO,CAAC,CAAC;CACvE,MAAM,UAAU,QAAQ,UAAU,CAAC;AACnC,KAAI,CAAC,QAAS,QAAO,OAAO,OAAO,KAAK,YAAY,CAAC;AAErD,QAAO;EAAE,WAAW;EAAM,QAAQ;EAAY,UAD/B,OAAO,YAAY,QAAQ,EACoB;EAAI;EAClE;AAEF,MAAa,QAAQ,OAAO,GAAG,aAAa,CAAC,aAAa;CACxD,MAAM,aAAa,OAAO,OAAO,OAAO,OAAO,OAAO,6BAA6B,CAAC;CACpF,MAAM,UAAU,OAAO,OAAO,eAAe,eAAe,SAAS,OAAO;EAAE,UAAU;EAAO,gBAAgB,OAAO,eAAe,WAAW;EAAE,CAAC,CAAC,GAAE,cAAW,OAAO,cAAcC,UAAQ,OAAO,CAAC,CAAC;CACvM,MAAM,UAAU,OAAO,eAAe,QAAQ,WAAW,EAAE,WAAW,gDAAgD,CAAC,CAAC;CACxH,MAAM,OAAO,OAAO,eAAe,QAAQ,SAAS,CAAC;AACrD,QAAO,eAAe,KAAK,KAAK,SAAS,WAAW;EAAE,WAAW;EAAoB,SAAS;EAAO,CAAC,CAAC;AACvG,QAAO,OAAO,WAAW,QAAQ,OAAO,MAAM,+DAA+D,CAAC;AAC9G,QAAO,eAAe,KAAK,YAAW,QAAO,IAAI,aAAa,qBAAqB,CAAC,IAAI,SAAS,WAAW,SAAS,IAAI,CAAC,IAAI,SAAS,WAAW,UAAU,EAAE,EAAE,SAAS,KAAQ,CAAC,CAAC;AAEnL,QAAO;EAAE,WAAW;EAAM,QAAQ;EAAY,UAD/B,OAAO,YAAY,QAAQ,EACoB;EAAI;EAClE"}
|
|
@@ -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";
|
|
@@ -68,7 +68,7 @@ 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({
|
|
@@ -77,36 +77,48 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
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." })
|
|
@@ -147,10 +159,18 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
147
159
|
});
|
|
148
160
|
return response.ok && new URL(response.url).pathname === "/home/";
|
|
149
161
|
})))) 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
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";
|
|
@@ -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-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({
|
|
@@ -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-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);
|
|
@@ -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-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;
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":["delay","cli","schema: { properties: Record<string, Property>; required?: string[] }","CliOption"],"sources":["../src/daemon.ts","../src/cli.ts"],"sourcesContent":["import { spawn, execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { chmod, mkdir, open, writeFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\nimport { defaultStateDirectory, origin } from './session-layer.js';\nimport { SessionError } from './errors.js';\n\nconst exec = promisify(execFile);\nconst daemonPath = () => fileURLToPath(new URL(`./daemon/galleton-${process.platform}-${process.arch}${process.platform === 'win32' ? '.exe' : ''}`, import.meta.url));\nconst failed = () => new SessionError({ message: 'Local daemon setup failed. Check semrush-cli daemon start and the private daemon.log in the state directory.' });\nconst io = <A>(run: () => Promise<A>) => Effect.tryPromise({ try: run, catch: failed });\n\n/** Own daemon, state directory and port. Never edits a shared Galleton installation. */\nexport const initializeDaemon = Effect.fn('Daemon.initialize')(function* () {\n yield* io(() => mkdir(defaultStateDirectory, { recursive: true, mode: 0o700 }));\n yield* io(() => chmod(defaultStateDirectory, 0o700));\n yield* io(() => exec(daemonPath(), ['init', '--dir', defaultStateDirectory]));\n const adapter = { providers: [{ name: 'semrush', kind: 'static', origins: [origin] }] };\n yield* io(() => writeFile(join(defaultStateDirectory, 'adapters.json'), JSON.stringify(adapter, null, 2) + '\\n', { mode: 0o600 }));\n});\n\nexport const startDaemon = Effect.fn('Daemon.start')(function* () {\n // A successful authenticated list proves this is the existing owned daemon.\n const listed = yield* io(() => exec(daemonPath(), ['list', '--dir', defaultStateDirectory, '--api', 'http://127.0.0.1:8768'])).pipe(Effect.match({ onSuccess: () => true, onFailure: () => false }));\n if (listed) return { running: true, url: 'http://127.0.0.1:8768' };\n yield* io(async () => {\n const log = await open(join(defaultStateDirectory, 'daemon.log'), 'a', 0o600);\n try {\n const child = spawn(daemonPath(), ['serve', '--dir', defaultStateDirectory, '--listen', '127.0.0.1:8768'], { detached: true, stdio: ['ignore', log.fd, log.fd] });\n await new Promise<void>((resolve, reject) => { child.once('spawn', resolve); child.once('error', reject); });\n child.unref();\n } finally { await log.close(); }\n });\n // Bounded startup readiness, without launching duplicate daemons on failure.\n yield* io(async () => {\n for (let attempt = 0; attempt < 30; attempt++) {\n try { await exec(daemonPath(), ['list', '--dir', defaultStateDirectory, '--api', 'http://127.0.0.1:8768']); return; }\n catch { await delay(100); }\n }\n throw new Error('Daemon did not become ready');\n });\n return { running: true, url: 'http://127.0.0.1:8768' };\n});\n\nexport const stopDaemon = Effect.fn('Daemon.stop')(function* () {\n yield* io(() => exec(daemonPath(), ['shutdown', '--dir', defaultStateDirectory, '--api', 'http://127.0.0.1:8768']));\n return { stopping: true };\n});\n\nexport const installBrowser = Effect.fn('Browser.install')(() => io(async () => {\n const cli = fileURLToPath(new URL('./cli.js', import.meta.resolve('patchright')));\n await exec(process.execPath, [cli, 'install', 'chromium'], { timeout: 180000, maxBuffer: 1024 * 1024 });\n return { installed: true };\n}));\n","#!/usr/bin/env node\nimport { Command, Option as CliOption, InvalidArgumentError, CommanderError } from 'commander';\nimport { Effect } from 'effect';\nimport type { Scope } from 'effect';\nimport { reports, version, type Query } from './report-parameters.js';\nimport { Sessions } from './session-service.js';\nimport { sessionsLive } from './session-layer.js';\nimport { initializeDaemon, startDaemon, stopDaemon, installBrowser } from './daemon.js';\n\nconst interruption = new AbortController();\nconst 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');\ncli.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');\n\nfunction output<A>(value: A, command: Command): void {\n process.stdout.write(JSON.stringify(value, null, command.optsWithGlobals().json ? undefined : 2) + '\\n');\n}\n\nasync function execute<A, E extends { readonly message: string }>(program: Effect.Effect<A, E, Scope.Scope>, command: Command): Promise<void> {\n await Effect.runPromise(Effect.scoped(program).pipe(Effect.match({\n onSuccess: value => output(value, command),\n onFailure: error => { process.stderr.write(`semrush-cli: ${error.message}\\n`); process.exitCode = 'name' in error && error.name === 'UsageError' ? 2 : 1; },\n })), { signal: interruption.signal });\n}\n\ncli.command('reports').description('List all 48 read-only research reports').action((_options, command: Command) => {\n output(Object.entries(reports).map(([name, report]) => ({ command: name.replaceAll('_', '-'), tool: name, description: report.description, schema: report.schema })), command);\n});\n\ninterface Property { type?: string; default?: unknown; description?: string; anyOf?: { type?: string }[]; }\nfor (const [name, definition] of Object.entries(reports)) {\n const report = cli.command(name.replaceAll('_', '-')).description(definition.description);\n const schema: { properties: Record<string, Property>; required?: string[] } = definition.schema;\n for (const [key, property] of Object.entries(schema.properties)) {\n const flag = key.replaceAll('_', '-');\n const description = (property.description ?? key.replaceAll('_', ' ')) + (property.default === undefined ? '' : ` (default: ${JSON.stringify(property.default)})`);\n const type = property.type ?? property.anyOf?.find(candidate => candidate.type !== 'null')?.type;\n const option = new CliOption(type === 'boolean' ? `--${flag} [boolean]` : `--${flag} <${type === 'array' || type === 'object' ? 'json' : 'value'}>`, description);\n option.argParser((text: string) => {\n if (type === 'boolean') {\n if (!['true', 'false'].includes(text)) throw new InvalidArgumentError('Expected true or false.');\n return text === 'true';\n }\n if (type === 'integer' || type === 'number') {\n if (!text.trim() || !Number.isFinite(Number(text))) throw new InvalidArgumentError('Expected a number.');\n return Number(text);\n }\n if (type === 'array' || type === 'object') {\n try { return JSON.parse(text); } catch { throw new InvalidArgumentError('Expected valid JSON.'); }\n }\n return text;\n });\n if (schema.required?.includes(key)) option.makeOptionMandatory();\n report.addOption(option);\n }\n report.action(async (_options, command: Command) => {\n const { parseReport } = await import('./validation.js');\n const options = command.opts<Query>();\n const input = Object.fromEntries(Object.keys(schema.properties).map(key => [key, options[key.replace(/_([a-z])/g, (_match, char: string) => char.toUpperCase())]]).filter(([, value]) => value !== undefined));\n // Parse before acquiring any credential-bearing browser resources.\n const parsed = await Effect.runPromise(parseReport(name, input).pipe(Effect.match({ onSuccess: value => ({ ok: true as const, value }), onFailure: error => ({ ok: false as const, error }) })));\n if (!parsed.ok) { process.stderr.write(`semrush-cli: ${parsed.error.message}\\n`); process.exitCode = 2; return; }\n const { appLayer } = await import('./mcp.js');\n const { Semrush } = await import('./browser-service.js');\n await execute(Effect.gen(function* () { return yield* (yield* Semrush).report(parsed.value); }).pipe(Effect.provide(appLayer)), command);\n });\n}\n\ncli.command('setup').description('Install Chromium and initialize the private Galleton daemon').action((_options, command: Command) => execute(Effect.gen(function* () {\n yield* initializeDaemon();\n yield* installBrowser();\n return { ...(yield* startDaemon()), next: 'semrush-cli auth login' };\n}), command));\nconst daemon = cli.command('daemon').description('Control the CLI-owned Galleton daemon');\ndaemon.command('start').description('Start the private daemon').action((_options, command: Command) => execute(startDaemon(), command));\ndaemon.command('stop').description('Gracefully stop the private daemon').action((_options, command: Command) => execute(stopDaemon(), command));\nconst auth = cli.command('auth').description('Store and inspect your Semrush session in Galleton');\nauth.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: { cdpUrl: string }, command: Command) => execute((await import('./auth.js')).importBrowser(options.cdpUrl).pipe(Effect.provide(sessionsLive)), command));\nauth.command('login').description('Sign in through an isolated visible Chromium window').action(async (_options, command: Command) => execute((await import('./auth.js')).login().pipe(Effect.provide(sessionsLive)), command));\nauth.command('status').description('Show session metadata without credentials').option('--check', 'Verify the stored session against Semrush').action((options: { check?: boolean }, command: Command) => execute(Effect.gen(function* () {\n const status = yield* (yield* Sessions).status();\n if (options.check) {\n const { appLayer } = yield* Effect.promise(() => import('./mcp.js'));\n const { Semrush } = yield* Effect.promise(() => import('./browser-service.js'));\n const { parseReport } = yield* Effect.promise(() => import('./validation.js'));\n yield* Effect.gen(function* () { return yield* (yield* Semrush).report(yield* parseReport('account_status', {})); }).pipe(Effect.provide(appLayer));\n }\n return { logged_in: status.status === 'ready', source: 'galleton', ...status, session_valid: options.check ? true : undefined };\n}).pipe(Effect.provide(sessionsLive)), command));\nauth.command('logout').description('Forget this CLI session from Galleton').action((_options, command: Command) => execute(Effect.gen(function* () { yield* (yield* Sessions).forget(); return { logged_in: false, forgotten: true }; }).pipe(Effect.provide(sessionsLive)), command));\ncli.command('doctor').description('Check Galleton, browser and authenticated report access').action(async (_options, command: Command) => {\n const { appLayer } = await import('./mcp.js');\n const { Semrush } = await import('./browser-service.js');\n const { parseReport } = await import('./validation.js');\n await execute(Effect.gen(function* () { yield* (yield* Semrush).report(yield* parseReport('account_status', {})); return { ok: true, galleton: true, chromium: true, authenticated: true }; }).pipe(Effect.provide(appLayer)), command);\n});\ncli.command('mcp').description('Run the read-only MCP server over stdio').action(async () => (await import('./mcp.js')).serveMcp());\n\nprocess.once('SIGINT', () => { if (process.argv.includes('mcp')) return; process.exitCode = 130; interruption.abort(); });\ntry { await cli.parseAsync(); }\ncatch (error) {\n // Commander writes usage details itself. Other defects must not expose browser payloads.\n if (interruption.signal.aborted) process.exitCode = 130;\n else if (error instanceof CommanderError) process.exitCode = error.exitCode === 0 ? 0 : 2;\n else { 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'); process.exitCode = 1; }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":["delay","cli","schema: { properties: Record<string, Property>; required?: string[] }","CliOption"],"sources":["../src/daemon.ts","../src/cli.ts"],"sourcesContent":["import { spawn, execFile } from 'node:child_process';\nimport { promisify } from 'node:util';\nimport { setTimeout as delay } from 'node:timers/promises';\nimport { chmod, mkdir, open, writeFile } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport { join } from 'node:path';\nimport { Effect } from 'effect';\nimport { defaultStateDirectory, origin } from './session-layer.js';\nimport { SessionError } from './errors.js';\n\nconst exec = promisify(execFile);\nconst daemonPath = () => fileURLToPath(new URL(`./daemon/galleton-${process.platform}-${process.arch}${process.platform === 'win32' ? '.exe' : ''}`, import.meta.url));\nconst failed = () => new SessionError({ message: 'Local daemon setup failed. Check semrush-cli daemon start and the private daemon.log in the state directory.' });\nconst io = <A>(run: () => Promise<A>) => Effect.tryPromise({ try: run, catch: failed });\n\n/** Own daemon, state directory and port. Never edits a shared Galleton installation. */\nexport const initializeDaemon = Effect.fn('Daemon.initialize')(function* () {\n yield* io(() => mkdir(defaultStateDirectory, { recursive: true, mode: 0o700 }));\n yield* io(() => chmod(defaultStateDirectory, 0o700));\n yield* io(() => exec(daemonPath(), ['init', '--dir', defaultStateDirectory]));\n const adapter = { providers: [{ name: 'semrush', kind: 'static', origins: [origin] }] };\n yield* io(() => writeFile(join(defaultStateDirectory, 'adapters.json'), JSON.stringify(adapter, null, 2) + '\\n', { mode: 0o600 }));\n});\n\nexport const startDaemon = Effect.fn('Daemon.start')(function* () {\n // A successful authenticated list proves this is the existing owned daemon.\n const listed = yield* io(() => exec(daemonPath(), ['list', '--dir', defaultStateDirectory, '--api', 'http://127.0.0.1:8768'])).pipe(Effect.match({ onSuccess: () => true, onFailure: () => false }));\n if (listed) return { running: true, url: 'http://127.0.0.1:8768' };\n yield* io(async () => {\n const log = await open(join(defaultStateDirectory, 'daemon.log'), 'a', 0o600);\n try {\n const child = spawn(daemonPath(), ['serve', '--dir', defaultStateDirectory, '--listen', '127.0.0.1:8768'], { detached: true, stdio: ['ignore', log.fd, log.fd] });\n await new Promise<void>((resolve, reject) => { child.once('spawn', resolve); child.once('error', reject); });\n child.unref();\n } finally { await log.close(); }\n });\n // Bounded startup readiness, without launching duplicate daemons on failure.\n yield* io(async () => {\n for (let attempt = 0; attempt < 30; attempt++) {\n try { await exec(daemonPath(), ['list', '--dir', defaultStateDirectory, '--api', 'http://127.0.0.1:8768']); return; }\n catch { await delay(100); }\n }\n throw new Error('Daemon did not become ready');\n });\n return { running: true, url: 'http://127.0.0.1:8768' };\n});\n\nexport const stopDaemon = Effect.fn('Daemon.stop')(function* () {\n yield* io(() => exec(daemonPath(), ['shutdown', '--dir', defaultStateDirectory, '--api', 'http://127.0.0.1:8768']));\n return { stopping: true };\n});\n\nexport const installBrowser = Effect.fn('Browser.install')(() => io(async () => {\n const cli = fileURLToPath(new URL('./cli.js', import.meta.resolve('patchright')));\n await exec(process.execPath, [cli, 'install', 'chromium'], { timeout: 180000, maxBuffer: 1024 * 1024 });\n return { installed: true };\n}));\n","#!/usr/bin/env node\nimport { Command, Option as CliOption, InvalidArgumentError, CommanderError } from 'commander';\nimport { Effect } from 'effect';\nimport type { Scope } from 'effect';\nimport { reports, version, previewMutation, type Query } from './report-parameters.js';\nimport { Sessions } from './session-service.js';\nimport { sessionsLive } from './session-layer.js';\nimport { initializeDaemon, startDaemon, stopDaemon, installBrowser } from './daemon.js';\n\nconst interruption = new AbortController();\nconst 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');\ncli.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');\n\nfunction output<A>(value: A, command: Command): void {\n process.stdout.write(JSON.stringify(value, null, command.optsWithGlobals().json ? undefined : 2) + '\\n');\n}\n\nasync function execute<A, E extends { readonly message: string }>(program: Effect.Effect<A, E, Scope.Scope>, command: Command): Promise<void> {\n await Effect.runPromise(Effect.scoped(program).pipe(Effect.match({\n onSuccess: value => output(value, command),\n onFailure: error => { process.stderr.write(`semrush-cli: ${error.message}\\n`); process.exitCode = 'name' in error && error.name === 'UsageError' ? 2 : 1; },\n })), { signal: interruption.signal });\n}\n\ncli.command('reports').description('List research reports and project operations').action((_options, command: Command) => {\n output(Object.entries(reports).map(([name, report]) => ({ command: name.replaceAll('_', '-'), tool: name, description: report.description, read_only: !report.mutation, destructive: report.destructive, schema: report.schema })), command);\n});\n\ninterface Property { type?: string; default?: unknown; description?: string; anyOf?: { type?: string }[]; }\nfor (const [name, definition] of Object.entries(reports)) {\n const report = cli.command(name.replaceAll('_', '-')).description(definition.description);\n const schema: { properties: Record<string, Property>; required?: string[] } = definition.schema;\n for (const [key, property] of Object.entries(schema.properties)) {\n const flag = key.replaceAll('_', '-');\n const description = (property.description ?? key.replaceAll('_', ' ')) + (property.default === undefined ? '' : ` (default: ${JSON.stringify(property.default)})`);\n const type = property.type ?? property.anyOf?.find(candidate => candidate.type !== 'null')?.type;\n const option = new CliOption(type === 'boolean' ? `--${flag} [boolean]` : `--${flag} <${type === 'array' || type === 'object' ? 'json' : 'value'}>`, description);\n option.argParser((text: string) => {\n if (type === 'boolean') {\n if (!['true', 'false'].includes(text)) throw new InvalidArgumentError('Expected true or false.');\n return text === 'true';\n }\n if (type === 'integer' || type === 'number') {\n if (!text.trim() || !Number.isFinite(Number(text))) throw new InvalidArgumentError('Expected a number.');\n return Number(text);\n }\n if (type === 'array' || type === 'object') {\n try { return JSON.parse(text); } catch { throw new InvalidArgumentError('Expected valid JSON.'); }\n }\n return text;\n });\n if (schema.required?.includes(key)) option.makeOptionMandatory();\n report.addOption(option);\n }\n report.action(async (_options, command: Command) => {\n const { parseReport } = await import('./validation.js');\n const options = command.opts<Query>();\n const input = Object.fromEntries(Object.keys(schema.properties).map(key => [key, options[key.replace(/_([a-z])/g, (_match, char: string) => char.toUpperCase())]]).filter(([, value]) => value !== undefined));\n // Parse before acquiring any credential-bearing browser resources.\n const parsed = await Effect.runPromise(parseReport(name, input).pipe(Effect.match({ onSuccess: value => ({ ok: true as const, value }), onFailure: error => ({ ok: false as const, error }) })));\n if (!parsed.ok) { process.stderr.write(`semrush-cli: ${parsed.error.message}\\n`); process.exitCode = 2; return; }\n const preview = previewMutation(parsed.value.name, parsed.value.definition, parsed.value.query);\n if (preview) { output(preview, command); return; }\n const { appLayer } = await import('./mcp.js');\n const { Semrush } = await import('./browser-service.js');\n await execute(Effect.gen(function* () { return yield* (yield* Semrush).report(parsed.value); }).pipe(Effect.provide(appLayer)), command);\n });\n}\n\ncli.command('setup').description('Install Chromium and initialize the private Galleton daemon').action((_options, command: Command) => execute(Effect.gen(function* () {\n yield* initializeDaemon();\n yield* installBrowser();\n return { ...(yield* startDaemon()), next: 'semrush-cli auth login' };\n}), command));\nconst daemon = cli.command('daemon').description('Control the CLI-owned Galleton daemon');\ndaemon.command('start').description('Start the private daemon').action((_options, command: Command) => execute(startDaemon(), command));\ndaemon.command('stop').description('Gracefully stop the private daemon').action((_options, command: Command) => execute(stopDaemon(), command));\nconst auth = cli.command('auth').description('Store and inspect your Semrush session in Galleton');\nauth.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: { cdpUrl: string }, command: Command) => execute((await import('./auth.js')).importBrowser(options.cdpUrl).pipe(Effect.provide(sessionsLive)), command));\nauth.command('login').description('Sign in through an isolated visible Chromium window').action(async (_options, command: Command) => execute((await import('./auth.js')).login().pipe(Effect.provide(sessionsLive)), command));\nauth.command('status').description('Show session metadata without credentials').option('--check', 'Verify the stored session against Semrush').action((options: { check?: boolean }, command: Command) => execute(Effect.gen(function* () {\n const status = yield* (yield* Sessions).status();\n if (options.check) {\n const { appLayer } = yield* Effect.promise(() => import('./mcp.js'));\n const { Semrush } = yield* Effect.promise(() => import('./browser-service.js'));\n const { parseReport } = yield* Effect.promise(() => import('./validation.js'));\n yield* Effect.gen(function* () { return yield* (yield* Semrush).report(yield* parseReport('account_status', {})); }).pipe(Effect.provide(appLayer));\n }\n return { logged_in: status.status === 'ready', source: 'galleton', ...status, session_valid: options.check ? true : undefined };\n}).pipe(Effect.provide(sessionsLive)), command));\nauth.command('logout').description('Forget this CLI session from Galleton').action((_options, command: Command) => execute(Effect.gen(function* () { yield* (yield* Sessions).forget(); return { logged_in: false, forgotten: true }; }).pipe(Effect.provide(sessionsLive)), command));\ncli.command('doctor').description('Check Galleton, browser and authenticated report access').action(async (_options, command: Command) => {\n const { appLayer } = await import('./mcp.js');\n const { Semrush } = await import('./browser-service.js');\n const { parseReport } = await import('./validation.js');\n await execute(Effect.gen(function* () { yield* (yield* Semrush).report(yield* parseReport('account_status', {})); return { ok: true, galleton: true, chromium: true, authenticated: true }; }).pipe(Effect.provide(appLayer)), command);\n});\ncli.command('mcp').description('Run the MCP server over stdio').action(async () => (await import('./mcp.js')).serveMcp());\n\nprocess.once('SIGINT', () => { if (process.argv.includes('mcp')) return; process.exitCode = 130; interruption.abort(); });\ntry { await cli.parseAsync(); }\ncatch (error) {\n // Commander writes usage details itself. Other defects must not expose browser payloads.\n if (interruption.signal.aborted) process.exitCode = 130;\n else if (error instanceof CommanderError) process.exitCode = error.exitCode === 0 ? 0 : 2;\n else { 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'); process.exitCode = 1; }\n}\n"],"mappings":"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"}
|