semrush-cli 0.1.0 → 0.1.1
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 +26 -24
- package/dist/{auth-B26NjmuZ.js → auth-BcUPx0Nk.js} +4 -4
- package/dist/{auth-B26NjmuZ.js.map → auth-BcUPx0Nk.js.map} +1 -1
- package/dist/{browser-layer-DijkeDly.js → browser-layer-D6mEyv9Y.js} +6 -6
- package/dist/browser-layer-D6mEyv9Y.js.map +1 -0
- package/dist/cli.js +18 -18
- package/dist/cli.js.map +1 -1
- package/dist/{errors-aN5dmXgv.js → errors-BhvanypE.js} +7 -2
- package/dist/errors-BhvanypE.js.map +1 -0
- package/dist/{mcp-DUyKjMzQ.js → mcp-Cuzenjra.js} +7 -7
- package/dist/mcp-Cuzenjra.js.map +1 -0
- package/dist/{session-layer-u9NMfM8W.js → session-layer-BUU0ZG9M.js} +5 -5
- package/dist/{session-layer-u9NMfM8W.js.map → session-layer-BUU0ZG9M.js.map} +1 -1
- package/dist/validation-BH5mq69L.js +4 -0
- package/dist/{validation-BIbDUKRg.js → validation-C_lcBSzX.js} +4 -4
- package/dist/validation-C_lcBSzX.js.map +1 -0
- package/package.json +1 -2
- package/dist/browser-layer-DijkeDly.js.map +0 -1
- package/dist/errors-aN5dmXgv.js.map +0 -1
- package/dist/mcp-DUyKjMzQ.js.map +0 -1
- package/dist/validation-BIbDUKRg.js.map +0 -1
- package/dist/validation-D4jV0Q8q.js +0 -4
package/README.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
<p align="center">Semrush research from your terminal and MCP clients.</p>
|
|
4
4
|
<p align="center"><a href="https://semrush.micr.dev">Documentation</a> · <a href="https://www.npmjs.com/package/semrush-cli">npm</a> · <a href="https://github.com/Microck/semrush-cli/issues">Issues</a></p>
|
|
5
5
|
|
|
6
|
+
The executable is `semrush-cli`. It can coexist with other tools named `semrush`.
|
|
7
|
+
|
|
6
8
|
48 read-only web reports for keyword research, organic rankings, competitors,
|
|
7
9
|
keyword and backlink gaps, backlinks, historical/mobile research and AI visibility.
|
|
8
10
|
Written in TypeScript with Effect. Sessions live in an encrypted Galleton vault.
|
|
@@ -15,9 +17,9 @@ macOS x64/arm64 and Windows x64.
|
|
|
15
17
|
|
|
16
18
|
```sh
|
|
17
19
|
npm install -g semrush-cli
|
|
18
|
-
semrush setup
|
|
19
|
-
semrush auth login
|
|
20
|
-
semrush domain-overview --domain openai.com --json
|
|
20
|
+
semrush-cli setup
|
|
21
|
+
semrush-cli auth login
|
|
22
|
+
semrush-cli domain-overview --domain openai.com --json
|
|
21
23
|
```
|
|
22
24
|
|
|
23
25
|
`setup` installs Chromium and starts a private Galleton daemon. `auth login`
|
|
@@ -28,8 +30,8 @@ libraries can be installed with `npx patchright install-deps chromium`.
|
|
|
28
30
|
To import an existing, authorized Chromium session on a headless machine:
|
|
29
31
|
|
|
30
32
|
```sh
|
|
31
|
-
semrush auth import --cdp-url http://127.0.0.1:9234
|
|
32
|
-
semrush doctor --json
|
|
33
|
+
semrush-cli auth import --cdp-url http://127.0.0.1:9234
|
|
34
|
+
semrush-cli doctor --json
|
|
33
35
|
```
|
|
34
36
|
|
|
35
37
|
The browser must already expose CDP on a literal loopback HTTP address. This
|
|
@@ -40,17 +42,17 @@ SSH when necessary.
|
|
|
40
42
|
## Research
|
|
41
43
|
|
|
42
44
|
```sh
|
|
43
|
-
semrush keyword-overview --keyword 'email marketing' --json
|
|
44
|
-
semrush keyword-magic --keyword 'email marketing' --questions-only --page-size 20 --json
|
|
45
|
-
semrush domain-keywords --domain openai.com --page 2 --page-size 20 --json
|
|
46
|
-
semrush domain-competitors --domain openai.com --page-size 10 --json
|
|
47
|
-
semrush keyword-gap --domains '["openai.com","anthropic.com"]' --json
|
|
48
|
-
semrush backlink-gap --domains '["openai.com","anthropic.com"]' --json
|
|
49
|
-
semrush backlink-links --domain openai.com --filters '+|type||nofollow' --json
|
|
50
|
-
semrush domain-ai-visibility --domain openai.com --json
|
|
51
|
-
semrush snapshot-dates --database us --json
|
|
52
|
-
semrush reports --json
|
|
53
|
-
semrush domain-keywords --help
|
|
45
|
+
semrush-cli keyword-overview --keyword 'email marketing' --json
|
|
46
|
+
semrush-cli keyword-magic --keyword 'email marketing' --questions-only --page-size 20 --json
|
|
47
|
+
semrush-cli domain-keywords --domain openai.com --page 2 --page-size 20 --json
|
|
48
|
+
semrush-cli domain-competitors --domain openai.com --page-size 10 --json
|
|
49
|
+
semrush-cli keyword-gap --domains '["openai.com","anthropic.com"]' --json
|
|
50
|
+
semrush-cli backlink-gap --domains '["openai.com","anthropic.com"]' --json
|
|
51
|
+
semrush-cli backlink-links --domain openai.com --filters '+|type||nofollow' --json
|
|
52
|
+
semrush-cli domain-ai-visibility --domain openai.com --json
|
|
53
|
+
semrush-cli snapshot-dates --database us --json
|
|
54
|
+
semrush-cli reports --json
|
|
55
|
+
semrush-cli domain-keywords --help
|
|
54
56
|
```
|
|
55
57
|
|
|
56
58
|
| Area | Reports |
|
|
@@ -76,7 +78,7 @@ Complete setup and authentication, then configure your MCP client:
|
|
|
76
78
|
{
|
|
77
79
|
"mcpServers": {
|
|
78
80
|
"semrush": {
|
|
79
|
-
"command": "semrush",
|
|
81
|
+
"command": "semrush-cli",
|
|
80
82
|
"args": ["mcp"]
|
|
81
83
|
}
|
|
82
84
|
}
|
|
@@ -84,7 +86,7 @@ Complete setup and authentication, then configure your MCP client:
|
|
|
84
86
|
```
|
|
85
87
|
|
|
86
88
|
Use an absolute executable path if your client does not inherit the terminal's
|
|
87
|
-
PATH. `npx -y semrush-cli@0.1.
|
|
89
|
+
PATH. `npx -y semrush-cli@0.1.1 mcp` also works. MCP tool names use underscores,
|
|
88
90
|
for example `keyword_overview` and `domain_keywords`. Input schemas and reports
|
|
89
91
|
match the CLI. All 48 tools are annotated read-only. Login, account mutations,
|
|
90
92
|
arbitrary URLs and JavaScript execution are not MCP tools.
|
|
@@ -92,10 +94,10 @@ arbitrary URLs and JavaScript execution are not MCP tools.
|
|
|
92
94
|
## Sessions
|
|
93
95
|
|
|
94
96
|
```sh
|
|
95
|
-
semrush auth status --check --json
|
|
96
|
-
semrush auth logout
|
|
97
|
-
semrush daemon stop
|
|
98
|
-
semrush daemon start
|
|
97
|
+
semrush-cli auth status --check --json
|
|
98
|
+
semrush-cli auth logout
|
|
99
|
+
semrush-cli daemon stop
|
|
100
|
+
semrush-cli daemon start
|
|
99
101
|
```
|
|
100
102
|
|
|
101
103
|
Galleton stores credentials under `~/.local/share/semrush-cli/galleton`. The
|
|
@@ -153,8 +155,8 @@ the separate live-account evidence.
|
|
|
153
155
|
## Uninstall
|
|
154
156
|
|
|
155
157
|
```sh
|
|
156
|
-
semrush auth logout
|
|
157
|
-
semrush daemon stop
|
|
158
|
+
semrush-cli auth logout
|
|
159
|
+
semrush-cli daemon stop
|
|
158
160
|
npm uninstall -g semrush-cli
|
|
159
161
|
```
|
|
160
162
|
|
|
@@ -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-BhvanypE.js";
|
|
2
|
+
import { i as Sessions, n as origin } from "./session-layer-BUU0ZG9M.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-D6mEyv9Y.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-BcUPx0Nk.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-
|
|
1
|
+
{"version":3,"file":"auth-BcUPx0Nk.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 { i as reportParameters, n as SessionError, t as ReportError } from "./errors-
|
|
2
|
-
import { i as Sessions, n as origin } from "./session-layer-
|
|
1
|
+
import { i as reportParameters, n as SessionError, t as ReportError } from "./errors-BhvanypE.js";
|
|
2
|
+
import { i as Sessions, n as origin } from "./session-layer-BUU0ZG9M.js";
|
|
3
3
|
import { t as Semrush } from "./browser-service-BYYE-vSm.js";
|
|
4
4
|
import { Config, Effect, Layer, Option, Predicate, Schema, Semaphore } from "effect";
|
|
5
5
|
import { chromium } from "patchright";
|
|
@@ -18,7 +18,7 @@ const json = JSONbig({
|
|
|
18
18
|
protoAction: "error",
|
|
19
19
|
constructorAction: "error"
|
|
20
20
|
});
|
|
21
|
-
const safeBrowserError = () => new ReportError({ message: "Semrush browser request failed. Run semrush doctor. If your session expired, sign in again with semrush auth login." });
|
|
21
|
+
const safeBrowserError = () => new ReportError({ message: "Semrush browser request failed. Run semrush-cli doctor. If your session expired, sign in again with semrush-cli auth login." });
|
|
22
22
|
const browserCall = (run) => Effect.tryPromise({
|
|
23
23
|
try: run,
|
|
24
24
|
catch: safeBrowserError
|
|
@@ -73,7 +73,7 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
73
73
|
}));
|
|
74
74
|
const auth = yield* Effect.try({
|
|
75
75
|
try: () => readBootstrap(bootstrap.text),
|
|
76
|
-
catch: () => new SessionError({ message: "Semrush sign-in metadata unavailable. Run semrush auth login or import a signed-in browser." })
|
|
76
|
+
catch: () => new SessionError({ message: "Semrush sign-in metadata unavailable. Run semrush-cli auth login or import a signed-in browser." })
|
|
77
77
|
});
|
|
78
78
|
const semaphore = yield* Semaphore.make(1);
|
|
79
79
|
let spectrumToken = "";
|
|
@@ -146,7 +146,7 @@ const makeSemrushLayer = (browserOrigin) => Layer.effect(Semrush, Effect.gen(fun
|
|
|
146
146
|
signal: AbortSignal.timeout(15e3)
|
|
147
147
|
});
|
|
148
148
|
return response.ok && new URL(response.url).pathname === "/home/";
|
|
149
|
-
})))) return yield* Effect.fail(new SessionError({ message: "Semrush session expired. Run semrush auth login or import a signed-in browser." }));
|
|
149
|
+
})))) return yield* Effect.fail(new SessionError({ message: "Semrush session expired. Run semrush-cli auth login or import a signed-in browser." }));
|
|
150
150
|
const params = reportParameters(definition, query);
|
|
151
151
|
const family = definition.transport;
|
|
152
152
|
let reportData;
|
|
@@ -266,4 +266,4 @@ const captureCookies = Effect.fn("Semrush.captureCookies")(function* (context, c
|
|
|
266
266
|
|
|
267
267
|
//#endregion
|
|
268
268
|
export { semrushLive as n, readBootstrap as t };
|
|
269
|
-
//# sourceMappingURL=browser-layer-
|
|
269
|
+
//# sourceMappingURL=browser-layer-D6mEyv9Y.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"browser-layer-D6mEyv9Y.js","names":["browser","payload: Schema.JsonObject","get","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, 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: response.ok ? 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, get = false, extraHeaders: Record<string, string> = {}) {\n const wire = yield* browserCall(() => page.evaluate(async ({ path, encodedPayload, get, extraHeaders }) => {\n const payload: Schema.JsonObject = JSON.parse(encodedPayload);\n const query = new URLSearchParams();\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: get ? 'GET' : 'POST', credentials: 'include', headers: extraHeaders, signal: AbortSignal.timeout(45000) };\n if (!get) { options.headers = { ...extraHeaders, 'Content-Type': 'application/json' }; options.body = JSON.stringify(payload); }\n const response = await fetch(get ? path + '?' + query : path, options);\n return { status: response.status, text: response.ok ? await response.text() : '' };\n }, { path, encodedPayload: JSON.stringify(payload), get, extraHeaders }));\n if (wire.status !== 200) return yield* Effect.fail(new ReportError({ message: `Semrush report returned HTTP ${wire.status}. Check your session and account permissions.` }));\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 params = reportParameters(definition, query);\n const family = definition.transport;\n let reportData: Schema.Json | undefined;\n 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, true, extra);\n }\n } else if (path.startsWith('/siteaudit/')) params.key = auth.apiKey;\n reportData = yield* request(path, params, !['post', 'gap', 'projects'].includes(family), 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 { a as reports, n as SessionError } from "./errors-
|
|
3
|
-
import { i as Sessions, n as origin, r as sessionsLive, t as defaultStateDirectory } from "./session-layer-
|
|
2
|
+
import { a as reports, n as SessionError, o as version } from "./errors-BhvanypE.js";
|
|
3
|
+
import { i as Sessions, n as origin, r as sessionsLive, t as defaultStateDirectory } from "./session-layer-BUU0ZG9M.js";
|
|
4
4
|
import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
|
|
5
5
|
import { Effect } from "effect";
|
|
6
6
|
import { chmod, mkdir, open, writeFile } from "node:fs/promises";
|
|
@@ -13,7 +13,7 @@ import { fileURLToPath } from "node:url";
|
|
|
13
13
|
//#region src/daemon.ts
|
|
14
14
|
const exec = promisify(execFile);
|
|
15
15
|
const daemonPath = () => fileURLToPath(new URL(`./daemon/galleton-${process.platform}-${process.arch}${process.platform === "win32" ? ".exe" : ""}`, import.meta.url));
|
|
16
|
-
const failed = () => new SessionError({ message: "Local daemon setup failed. Check semrush daemon start and the private daemon.log in the state directory." });
|
|
16
|
+
const failed = () => new SessionError({ message: "Local daemon setup failed. Check semrush-cli daemon start and the private daemon.log in the state directory." });
|
|
17
17
|
const io = (run) => Effect.tryPromise({
|
|
18
18
|
try: run,
|
|
19
19
|
catch: failed
|
|
@@ -123,8 +123,8 @@ const installBrowser = Effect.fn("Browser.install")(() => io(async () => {
|
|
|
123
123
|
//#endregion
|
|
124
124
|
//#region src/cli.ts
|
|
125
125
|
const interruption = new AbortController();
|
|
126
|
-
const cli = new Command().name("semrush").description("Semrush research through your signed-in account. CLI + MCP, powered by Effect and Galleton.").version(
|
|
127
|
-
cli.addHelpText("after", "\nExamples:\n semrush setup\n semrush auth login\n semrush domain-overview --domain openai.com --json\n semrush keyword-magic --keyword \"email marketing\" --page-size 20 --json\n semrush mcp\n\nDocumentation: https://semrush.micr.dev\n");
|
|
126
|
+
const cli = new Command().name("semrush-cli").description("Semrush research through your signed-in account. CLI + MCP, powered by Effect and Galleton.").version(version).exitOverride().showHelpAfterError().option("--json", "Emit compact JSON").option("--no-input", "Never prompt for terminal input").option("--no-color", "Disable terminal colors");
|
|
127
|
+
cli.addHelpText("after", "\nExamples:\n semrush-cli setup\n semrush-cli auth login\n semrush-cli domain-overview --domain openai.com --json\n semrush-cli keyword-magic --keyword \"email marketing\" --page-size 20 --json\n semrush-cli mcp\n\nDocumentation: https://semrush.micr.dev\n");
|
|
128
128
|
function output(value, command) {
|
|
129
129
|
process.stdout.write(JSON.stringify(value, null, command.optsWithGlobals().json ? void 0 : 2) + "\n");
|
|
130
130
|
}
|
|
@@ -132,7 +132,7 @@ async function execute(program, command) {
|
|
|
132
132
|
await Effect.runPromise(Effect.scoped(program).pipe(Effect.match({
|
|
133
133
|
onSuccess: (value) => output(value, command),
|
|
134
134
|
onFailure: (error) => {
|
|
135
|
-
process.stderr.write(`semrush: ${error.message}\n`);
|
|
135
|
+
process.stderr.write(`semrush-cli: ${error.message}\n`);
|
|
136
136
|
process.exitCode = "name" in error && error.name === "UsageError" ? 2 : 1;
|
|
137
137
|
}
|
|
138
138
|
})), { signal: interruption.signal });
|
|
@@ -173,7 +173,7 @@ for (const [name, definition] of Object.entries(reports)) {
|
|
|
173
173
|
report.addOption(option);
|
|
174
174
|
}
|
|
175
175
|
report.action(async (_options, command) => {
|
|
176
|
-
const { parseReport } = await import("./validation-
|
|
176
|
+
const { parseReport } = await import("./validation-BH5mq69L.js");
|
|
177
177
|
const options = command.opts();
|
|
178
178
|
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
179
|
const parsed = await Effect.runPromise(parseReport(name, input).pipe(Effect.match({
|
|
@@ -187,11 +187,11 @@ for (const [name, definition] of Object.entries(reports)) {
|
|
|
187
187
|
})
|
|
188
188
|
})));
|
|
189
189
|
if (!parsed.ok) {
|
|
190
|
-
process.stderr.write(`semrush: ${parsed.error.message}\n`);
|
|
190
|
+
process.stderr.write(`semrush-cli: ${parsed.error.message}\n`);
|
|
191
191
|
process.exitCode = 2;
|
|
192
192
|
return;
|
|
193
193
|
}
|
|
194
|
-
const { appLayer } = await import("./mcp-
|
|
194
|
+
const { appLayer } = await import("./mcp-Cuzenjra.js");
|
|
195
195
|
const { Semrush } = await import("./browser-service-CL8YPaIT.js");
|
|
196
196
|
await execute(Effect.gen(function* () {
|
|
197
197
|
return yield* (yield* Semrush).report(parsed.value);
|
|
@@ -203,21 +203,21 @@ cli.command("setup").description("Install Chromium and initialize the private Ga
|
|
|
203
203
|
yield* installBrowser();
|
|
204
204
|
return {
|
|
205
205
|
...yield* startDaemon(),
|
|
206
|
-
next: "semrush auth login"
|
|
206
|
+
next: "semrush-cli auth login"
|
|
207
207
|
};
|
|
208
208
|
}), command));
|
|
209
209
|
const daemon = cli.command("daemon").description("Control the CLI-owned Galleton daemon");
|
|
210
210
|
daemon.command("start").description("Start the private daemon").action((_options, command) => execute(startDaemon(), command));
|
|
211
211
|
daemon.command("stop").description("Gracefully stop the private daemon").action((_options, command) => execute(stopDaemon(), command));
|
|
212
212
|
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-
|
|
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-BcUPx0Nk.js")).importBrowser(options.cdpUrl).pipe(Effect.provide(sessionsLive)), command));
|
|
214
|
+
auth.command("login").description("Sign in through an isolated visible Chromium window").action(async (_options, command) => execute((await import("./auth-BcUPx0Nk.js")).login().pipe(Effect.provide(sessionsLive)), command));
|
|
215
215
|
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
216
|
const status = yield* (yield* Sessions).status();
|
|
217
217
|
if (options.check) {
|
|
218
|
-
const { appLayer } = yield* Effect.promise(() => import("./mcp-
|
|
218
|
+
const { appLayer } = yield* Effect.promise(() => import("./mcp-Cuzenjra.js"));
|
|
219
219
|
const { Semrush } = yield* Effect.promise(() => import("./browser-service-CL8YPaIT.js"));
|
|
220
|
-
const { parseReport } = yield* Effect.promise(() => import("./validation-
|
|
220
|
+
const { parseReport } = yield* Effect.promise(() => import("./validation-BH5mq69L.js"));
|
|
221
221
|
yield* Effect.gen(function* () {
|
|
222
222
|
return yield* (yield* Semrush).report(yield* parseReport("account_status", {}));
|
|
223
223
|
}).pipe(Effect.provide(appLayer));
|
|
@@ -237,9 +237,9 @@ auth.command("logout").description("Forget this CLI session from Galleton").acti
|
|
|
237
237
|
};
|
|
238
238
|
}).pipe(Effect.provide(sessionsLive)), command));
|
|
239
239
|
cli.command("doctor").description("Check Galleton, browser and authenticated report access").action(async (_options, command) => {
|
|
240
|
-
const { appLayer } = await import("./mcp-
|
|
240
|
+
const { appLayer } = await import("./mcp-Cuzenjra.js");
|
|
241
241
|
const { Semrush } = await import("./browser-service-CL8YPaIT.js");
|
|
242
|
-
const { parseReport } = await import("./validation-
|
|
242
|
+
const { parseReport } = await import("./validation-BH5mq69L.js");
|
|
243
243
|
await execute(Effect.gen(function* () {
|
|
244
244
|
yield* (yield* Semrush).report(yield* parseReport("account_status", {}));
|
|
245
245
|
return {
|
|
@@ -250,7 +250,7 @@ cli.command("doctor").description("Check Galleton, browser and authenticated rep
|
|
|
250
250
|
};
|
|
251
251
|
}).pipe(Effect.provide(appLayer)), command);
|
|
252
252
|
});
|
|
253
|
-
cli.command("mcp").description("Run the read-only MCP server over stdio").action(async () => (await import("./mcp-
|
|
253
|
+
cli.command("mcp").description("Run the read-only MCP server over stdio").action(async () => (await import("./mcp-Cuzenjra.js")).serveMcp());
|
|
254
254
|
process.once("SIGINT", () => {
|
|
255
255
|
if (process.argv.includes("mcp")) return;
|
|
256
256
|
process.exitCode = 130;
|
|
@@ -262,7 +262,7 @@ try {
|
|
|
262
262
|
if (interruption.signal.aborted) process.exitCode = 130;
|
|
263
263
|
else if (error instanceof CommanderError) process.exitCode = error.exitCode === 0 ? 0 : 2;
|
|
264
264
|
else {
|
|
265
|
-
process.stderr.write("semrush: Unexpected failure. Run semrush doctor and report the failing command at https://github.com/Microck/semrush-cli/issues.\n");
|
|
265
|
+
process.stderr.write("semrush-cli: Unexpected failure. Run semrush-cli doctor and report the failing command at https://github.com/Microck/semrush-cli/issues.\n");
|
|
266
266
|
process.exitCode = 1;
|
|
267
267
|
}
|
|
268
268
|
}
|
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 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, 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').description('Semrush research through your signed-in account. CLI + MCP, powered by Effect and Galleton.').version('0.1.0').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 setup\\n semrush auth login\\n semrush domain-overview --domain openai.com --json\\n semrush keyword-magic --keyword \"email marketing\" --page-size 20 --json\\n semrush mcp\\n\\nDocumentation: https://semrush.micr.dev\\n');\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: ${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: ${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 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: Unexpected failure. Run semrush 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, 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,5 +1,9 @@
|
|
|
1
1
|
import { Schema } from "effect";
|
|
2
2
|
|
|
3
|
+
//#region package.json
|
|
4
|
+
var version$1 = "0.1.1";
|
|
5
|
+
|
|
6
|
+
//#endregion
|
|
3
7
|
//#region src/report-catalog.json
|
|
4
8
|
var report_catalog_default = {
|
|
5
9
|
account_status: {
|
|
@@ -1870,6 +1874,7 @@ const backlinkSorts = new Map([
|
|
|
1870
1874
|
["backlinks_pages", "domainsnum"],
|
|
1871
1875
|
["backlinks_outgoing_domains", "links_num"]
|
|
1872
1876
|
]);
|
|
1877
|
+
const version = version$1;
|
|
1873
1878
|
const reports = report_catalog_default;
|
|
1874
1879
|
/** Fixed request shapes observed in semrush-web-mcp. No arbitrary RPC or URL input. */
|
|
1875
1880
|
function reportParameters(definition, q) {
|
|
@@ -2115,5 +2120,5 @@ var ReportError = class extends Schema.TaggedError()("ReportError", { message: S
|
|
|
2115
2120
|
var UsageError = class extends Schema.TaggedError()("UsageError", { message: Schema.String }) {};
|
|
2116
2121
|
|
|
2117
2122
|
//#endregion
|
|
2118
|
-
export { reports as a, reportParameters as i, SessionError as n, UsageError as r, ReportError as t };
|
|
2119
|
-
//# sourceMappingURL=errors-
|
|
2123
|
+
export { reports as a, reportParameters as i, SessionError as n, version as o, UsageError as r, ReportError as t };
|
|
2124
|
+
//# sourceMappingURL=errors-BhvanypE.js.map
|