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 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.0 mcp` also works. MCP tool names use underscores,
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-aN5dmXgv.js";
2
- import { i as Sessions, n as origin } from "./session-layer-u9NMfM8W.js";
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-DijkeDly.js";
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-B26NjmuZ.js.map
82
+ //# sourceMappingURL=auth-BcUPx0Nk.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth-B26NjmuZ.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
+ {"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":";;;;;;;;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 { i as reportParameters, n as SessionError, t as ReportError } from "./errors-aN5dmXgv.js";
2
- import { i as Sessions, n as origin } from "./session-layer-u9NMfM8W.js";
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-DijkeDly.js.map
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-aN5dmXgv.js";
3
- import { i as Sessions, n as origin, r as sessionsLive, t as defaultStateDirectory } from "./session-layer-u9NMfM8W.js";
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("0.1.0").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 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-D4jV0Q8q.js");
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-DUyKjMzQ.js");
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-B26NjmuZ.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-B26NjmuZ.js")).login().pipe(Effect.provide(sessionsLive)), command));
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-DUyKjMzQ.js"));
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-D4jV0Q8q.js"));
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-DUyKjMzQ.js");
240
+ const { appLayer } = await import("./mcp-Cuzenjra.js");
241
241
  const { Semrush } = await import("./browser-service-CL8YPaIT.js");
242
- const { parseReport } = await import("./validation-D4jV0Q8q.js");
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-DUyKjMzQ.js")).serveMcp());
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-aN5dmXgv.js.map
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