semrush-cli 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +169 -165
- package/assets/demo-keywords.gif +0 -0
- package/assets/demo-keywords.png +0 -0
- package/assets/demo-projects.gif +0 -0
- package/assets/demo-projects.png +0 -0
- package/assets/logo-lockup.svg +10 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +4 -8
- package/dist/{auth-CHCXZZpR.js → auth-CrYOA8QT.js} +4 -4
- package/dist/{auth-CHCXZZpR.js.map → auth-CrYOA8QT.js.map} +1 -1
- package/dist/{browser-layer-DgW0vW92.js → browser-layer-gdm03TCV.js} +6 -6
- package/dist/browser-layer-gdm03TCV.js.map +1 -0
- package/dist/cli.js +18 -18
- package/dist/cli.js.map +1 -1
- package/dist/{errors-BjuHkMt_.js → errors-DNn_XeoU.js} +2 -2
- package/dist/errors-DNn_XeoU.js.map +1 -0
- package/dist/{mcp-BPznArmB.js → mcp-DqvGEnxx.js} +6 -6
- package/dist/{mcp-BPznArmB.js.map → mcp-DqvGEnxx.js.map} +1 -1
- package/dist/{session-layer-DJzN9Xx2.js → session-layer-Bq5OpnR5.js} +5 -5
- package/dist/{session-layer-DJzN9Xx2.js.map → session-layer-Bq5OpnR5.js.map} +1 -1
- package/dist/{validation-CenS7xI_.js → validation-B3hdOxdH.js} +4 -4
- package/dist/validation-B3hdOxdH.js.map +1 -0
- package/dist/validation-B8xjCr9w.js +4 -0
- package/package.json +2 -2
- package/dist/browser-layer-DgW0vW92.js.map +0 -1
- package/dist/errors-BjuHkMt_.js.map +0 -1
- package/dist/validation-B5M_ocvQ.js +0 -4
- package/dist/validation-CenS7xI_.js.map +0 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as SessionError } from "./errors-
|
|
1
|
+
import { n as SessionError } from "./errors-DNn_XeoU.js";
|
|
2
2
|
import { Config, Context, Effect, Layer } from "effect";
|
|
3
3
|
import { readFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
@@ -175,13 +175,13 @@ const defaultStateDirectory = join(homedir(), ".local", "share", "semrush-cli",
|
|
|
175
175
|
/** Raw SDK errors stop here; daemon and upstream payloads never reach diagnostics. */
|
|
176
176
|
const sdkError = (cause) => {
|
|
177
177
|
if (cause instanceof GalletonError) {
|
|
178
|
-
const recovery = cause.code === "not_found" ? "Run semrush
|
|
178
|
+
const recovery = cause.code === "not_found" ? "Run semrush auth login or semrush auth import --cdp-url URL." : "Run semrush daemon start and semrush auth status. Reimport if renewal or authentication failed.";
|
|
179
179
|
return new SessionError({
|
|
180
180
|
message: `Galleton ${cause.code}. ${recovery}`,
|
|
181
181
|
code: cause.code
|
|
182
182
|
});
|
|
183
183
|
}
|
|
184
|
-
return new SessionError({ message: "Galleton operation failed. Run semrush
|
|
184
|
+
return new SessionError({ message: "Galleton operation failed. Run semrush doctor and check local configuration." });
|
|
185
185
|
};
|
|
186
186
|
const makeSessionsLayer = (credentialOrigin) => Layer.effect(Sessions, Effect.gen(function* () {
|
|
187
187
|
const baseURL = yield* Config.String("SEMRUSH_GALLETON_URL").pipe(Config.withDefault(defaultDaemonURL));
|
|
@@ -189,7 +189,7 @@ const makeSessionsLayer = (credentialOrigin) => Layer.effect(Sessions, Effect.ge
|
|
|
189
189
|
const id = yield* Config.String("SEMRUSH_SESSION").pipe(Config.withDefault("semrush-cli"));
|
|
190
190
|
const token = yield* Effect.tryPromise({
|
|
191
191
|
try: () => readFile(tokenFile, "utf8"),
|
|
192
|
-
catch: () => new SessionError({ message: "Galleton token unavailable. Run semrush
|
|
192
|
+
catch: () => new SessionError({ message: "Galleton token unavailable. Run semrush setup, or set SEMRUSH_GALLETON_TOKEN_FILE." })
|
|
193
193
|
});
|
|
194
194
|
const sdk = yield* Effect.try({
|
|
195
195
|
try: () => new Galleton({
|
|
@@ -237,4 +237,4 @@ const sessionsLive = makeSessionsLayer(origin);
|
|
|
237
237
|
|
|
238
238
|
//#endregion
|
|
239
239
|
export { Sessions as a, sessionsLive as i, defaultStateDirectory as n, origin as r, defaultDaemonURL as t };
|
|
240
|
-
//# sourceMappingURL=session-layer-
|
|
240
|
+
//# sourceMappingURL=session-layer-Bq5OpnR5.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-layer-DJzN9Xx2.js","names":["code: string","retryAt?: string","status: number","headers: Record<string, string[]>","body: Uint8Array","revision: number","response: Response","text: string","decoded: unknown"],"sources":["../src/session-service.ts","../src/vendor/galleton.ts","../src/session-layer.ts"],"sourcesContent":["import { Context, Effect } from 'effect';\nimport type { Credentials, SessionStatus } from './vendor/galleton.js';\nimport type { SessionError } from './errors.js';\n\n/** Galleton owns all durable credentials. Consumers only receive scoped headers. */\nexport class Sessions extends Context.Service<Sessions, {\n readonly headers: () => Effect.Effect<Record<string, string>, SessionError>;\n readonly status: () => Effect.Effect<SessionStatus, SessionError>;\n readonly save: (credentials: Credentials) => Effect.Effect<SessionStatus, SessionError>;\n readonly capture: (cookies: string[]) => Effect.Effect<void, SessionError>;\n readonly forget: () => Effect.Effect<void, SessionError>;\n}>()('semrush/Sessions') {}\n","/** Server-side client for an authenticated, loopback-only Galleton daemon. */\nexport interface Credentials {\n provider: string;\n cookie_origin?: string;\n cookie_header?: string;\n set_cookies?: string[];\n access_token?: string;\n refresh_token?: string;\n access_expires_at?: string;\n secrets?: Record<string, string>;\n replace?: boolean;\n expected_revision?: number;\n}\nexport interface Problem { code: string; message: string; retry_at?: string }\nexport interface SessionStatus {\n id: string; provider: string; status: string; revision: number;\n created_at: string; last_refresh?: string; next_refresh?: string;\n access_expires_at?: string; cookie_count: number; error?: Problem;\n}\nexport class GalletonError extends Error {\n constructor(public readonly code: string, message: string,\n public readonly status = 0, public readonly retryAt?: string) {\n super(message); this.name = \"GalletonError\";\n }\n}\nexport interface RequestOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: Uint8Array | string;\n json?: unknown;\n}\nexport class ManagedResponse {\n constructor(public readonly status: number,\n public readonly headers: Record<string, string[]>,\n public readonly body: Uint8Array,\n public readonly revision: number) {}\n get ok(): boolean { return this.status >= 200 && this.status < 300; }\n text(): string { return new TextDecoder().decode(this.body); }\n json<T = unknown>(): T { return JSON.parse(this.text()) as T; }\n}\nfunction encode64(bytes: Uint8Array): string {\n let binary = \"\";\n for (let start = 0; start < bytes.length; start += 8192) {\n binary += String.fromCharCode(...bytes.subarray(start, start + 8192));\n }\n return btoa(binary);\n}\nfunction decode64(value: string): Uint8Array {\n return Uint8Array.from(atob(value), char => char.charCodeAt(0));\n}\nfunction sessionPath(id: string): string {\n if (!/^[A-Za-z0-9_-]{1,64}$/.test(id)) throw new TypeError(\"Invalid session ID\");\n return `/v1/sessions/${id}`;\n}\nasync function responseText(response: Response): Promise<string> {\n const reader = response.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder();\n let size = 0;\n let text = \"\";\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > 8 * 1024 * 1024) {\n await reader.cancel().catch(() => {});\n throw new GalletonError(\"invalid_response\", \"Oversized daemon response.\", response.status);\n }\n text += decoder.decode(value, { stream: true });\n }\n return text + decoder.decode();\n } finally {\n reader.releaseLock();\n }\n}\nexport class Galleton {\n private readonly base: string;\n private readonly token: string;\n private readonly timeoutMs: number;\n constructor(options: { token: string; baseURL?: string; timeoutMs?: number }) {\n const url = new URL(options.baseURL ?? \"http://127.0.0.1:8766\");\n const host = url.hostname;\n const loopback = host === \"[::1]\" ||\n /^127(?:\\.[0-9]{1,3}){3}$/.test(host) && host.split(\".\").every(n => +n <= 255);\n if (!loopback || url.protocol !== \"http:\" || url.username || url.password ||\n url.search || url.hash || url.pathname !== \"/\") {\n throw new TypeError(\"Galleton requires a literal loopback HTTP origin\");\n }\n this.base = url.origin;\n this.token = options.token.trim();\n if (!this.token || /[\\r\\n]/.test(this.token)) throw new TypeError(\"Invalid local API token\");\n this.timeoutMs = options.timeoutMs ?? 300_000;\n if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) throw new TypeError(\"Invalid timeout\");\n }\n private async call<T>(method: string, path: string, body?: unknown): Promise<T> {\n let response: Response;\n try {\n response = await fetch(this.base + path, {\n method,\n headers: { Authorization: `Bearer ${this.token}`, \"Content-Type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n redirect: \"manual\", signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch {\n throw new GalletonError(\"daemon_unavailable\", \"Galleton is unavailable or the local request timed out.\");\n }\n let text: string;\n try { text = await responseText(response); }\n catch (error) {\n if (error instanceof GalletonError) throw error;\n throw new GalletonError(\"daemon_unavailable\", \"Galleton response was interrupted; the request may already have completed.\", response.status);\n }\n let decoded: unknown;\n try { decoded = JSON.parse(text); }\n catch {\n if (!response.ok) throw new GalletonError(\"daemon_error\", \"Galleton request failed.\", response.status);\n throw new GalletonError(\"invalid_response\", \"Expected a JSON daemon response.\", response.status);\n }\n if (!response.ok) {\n const error = (decoded as { error?: Problem }).error;\n throw new GalletonError(error?.code ?? \"daemon_error\",\n error?.message ?? \"Galleton request failed.\", response.status, error?.retry_at);\n }\n return decoded as T;\n }\n connect(id: string, credentials: Credentials): Promise<SessionStatus> {\n return this.call(\"PUT\", sessionPath(id), credentials);\n }\n status(id: string): Promise<SessionStatus> { return this.call(\"GET\", sessionPath(id)); }\n async list(): Promise<SessionStatus[]> {\n return (await this.call<{ sessions: SessionStatus[] }>(\"GET\", \"/v1/sessions\")).sessions;\n }\n refresh(id: string): Promise<SessionStatus> { return this.call(\"POST\", sessionPath(id) + \"/refresh\", {}); }\n async headers(id: string, url: string): Promise<Record<string, string>> {\n return (await this.call<{ headers: Record<string, string> }>(\n \"POST\", sessionPath(id) + \"/headers\", { url })).headers;\n }\n capture(id: string, url: string, setCookie: string[]): Promise<SessionStatus> {\n return this.call(\"POST\", sessionPath(id) + \"/cookies\", { url, set_cookie: setCookie });\n }\n async forget(id: string): Promise<void> { await this.call(\"DELETE\", sessionPath(id)); }\n /** Buffered request with automatic Cookie/Authorization injection and Set-Cookie capture.\n * Resource calls are never automatically retried. Non-2xx upstream statuses are returned. */\n async request(id: string, url: string, options: RequestOptions = {}): Promise<ManagedResponse> {\n if (options.body !== undefined && options.json !== undefined) throw new TypeError(\"Use body or json, not both\");\n const headers = { ...options.headers };\n let body = options.body;\n if (options.json !== undefined) {\n body = JSON.stringify(options.json);\n for (const name of Object.keys(headers)) {\n if (name.toLowerCase() === \"content-type\") delete headers[name];\n }\n headers[\"Content-Type\"] = \"application/json\";\n }\n const bytes = typeof body === \"string\" ? new TextEncoder().encode(body) : body ?? new Uint8Array();\n if (bytes.length > 1024 * 1024) throw new TypeError(\"Request body exceeds 1 MiB\");\n const wire = await this.call<{\n status: number; headers: Record<string, string[]>; body_base64: string; revision: number;\n }>(\"POST\", sessionPath(id) + \"/request\", {\n url, method: options.method ?? \"GET\", headers, body_base64: encode64(bytes),\n });\n return new ManagedResponse(wire.status, wire.headers, decode64(wire.body_base64), wire.revision);\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { Config, Effect, Layer } from 'effect';\nimport { Galleton, GalletonError, type Credentials } from './vendor/galleton.js';\nimport { SessionError } from './errors.js';\nimport { Sessions } from './session-service.js';\n\nexport const origin = 'https://www.semrush.com';\nexport const defaultDaemonURL = 'http://127.0.0.1:8771';\nexport const defaultStateDirectory = join(homedir(), '.local', 'share', 'semrush-cli', 'galleton');\n\n/** Raw SDK errors stop here; daemon and upstream payloads never reach diagnostics. */\nconst sdkError = (cause: unknown): SessionError => {\n if (cause instanceof GalletonError) {\n const recovery = cause.code === 'not_found' ? 'Run semrush-cli auth login or semrush-cli auth import --cdp-url URL.' : 'Run semrush-cli daemon start and semrush-cli auth status. Reimport if renewal or authentication failed.';\n return new SessionError({ message: `Galleton ${cause.code}. ${recovery}`, code: cause.code });\n }\n return new SessionError({ message: 'Galleton operation failed. Run semrush-cli doctor and check local configuration.' });\n};\n\nexport const makeSessionsLayer = (credentialOrigin: string) => Layer.effect(Sessions, Effect.gen(function* () {\n const baseURL = yield* Config.String('SEMRUSH_GALLETON_URL').pipe(Config.withDefault(defaultDaemonURL));\n const tokenFile = yield* Config.String('SEMRUSH_GALLETON_TOKEN_FILE').pipe(Config.withDefault(join(defaultStateDirectory, 'api.token')));\n const id = yield* Config.String('SEMRUSH_SESSION').pipe(Config.withDefault('semrush-cli'));\n const token = yield* Effect.tryPromise({ try: () => readFile(tokenFile, 'utf8'), catch: () => new SessionError({ message: 'Galleton token unavailable. Run semrush-cli setup, or set SEMRUSH_GALLETON_TOKEN_FILE.' }) });\n const sdk = yield* Effect.try({ try: () => new Galleton({ baseURL, token, timeoutMs: 60000 }), catch: sdkError });\n const status = Effect.fn('Sessions.status')(() => Effect.tryPromise({ try: () => sdk.status(id), catch: sdkError }));\n return Sessions.of({\n status,\n headers: Effect.fn('Sessions.headers')(() => Effect.tryPromise({ try: () => sdk.headers(id, credentialOrigin + '/'), catch: sdkError })),\n save: Effect.fn('Sessions.save')(function* (credentials: Credentials) {\n // Replacements are explicit and revision-checked; missing profiles are created.\n const previous = yield* status().pipe(Effect.map(value => value.revision), Effect.catchTag('SessionError', error => error.code === 'not_found' ? Effect.succeed(undefined) : Effect.fail(error)));\n const input = { ...credentials };\n if (previous !== undefined) { input.replace = true; input.expected_revision = previous; }\n return yield* Effect.tryPromise({ try: () => sdk.connect(id, input), catch: sdkError });\n }),\n capture: Effect.fn('Sessions.capture')((cookies: string[]) => Effect.tryPromise({ try: async () => { await sdk.capture(id, credentialOrigin + '/', cookies); }, catch: sdkError })),\n forget: Effect.fn('Sessions.forget')(() => Effect.tryPromise({ try: () => sdk.forget(id), catch: sdkError })),\n });\n}));\n\nexport const sessionsLive = makeSessionsLayer(origin);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-layer-Bq5OpnR5.js","names":["code: string","retryAt?: string","status: number","headers: Record<string, string[]>","body: Uint8Array","revision: number","response: Response","text: string","decoded: unknown"],"sources":["../src/session-service.ts","../src/vendor/galleton.ts","../src/session-layer.ts"],"sourcesContent":["import { Context, Effect } from 'effect';\nimport type { Credentials, SessionStatus } from './vendor/galleton.js';\nimport type { SessionError } from './errors.js';\n\n/** Galleton owns all durable credentials. Consumers only receive scoped headers. */\nexport class Sessions extends Context.Service<Sessions, {\n readonly headers: () => Effect.Effect<Record<string, string>, SessionError>;\n readonly status: () => Effect.Effect<SessionStatus, SessionError>;\n readonly save: (credentials: Credentials) => Effect.Effect<SessionStatus, SessionError>;\n readonly capture: (cookies: string[]) => Effect.Effect<void, SessionError>;\n readonly forget: () => Effect.Effect<void, SessionError>;\n}>()('semrush/Sessions') {}\n","/** Server-side client for an authenticated, loopback-only Galleton daemon. */\nexport interface Credentials {\n provider: string;\n cookie_origin?: string;\n cookie_header?: string;\n set_cookies?: string[];\n access_token?: string;\n refresh_token?: string;\n access_expires_at?: string;\n secrets?: Record<string, string>;\n replace?: boolean;\n expected_revision?: number;\n}\nexport interface Problem { code: string; message: string; retry_at?: string }\nexport interface SessionStatus {\n id: string; provider: string; status: string; revision: number;\n created_at: string; last_refresh?: string; next_refresh?: string;\n access_expires_at?: string; cookie_count: number; error?: Problem;\n}\nexport class GalletonError extends Error {\n constructor(public readonly code: string, message: string,\n public readonly status = 0, public readonly retryAt?: string) {\n super(message); this.name = \"GalletonError\";\n }\n}\nexport interface RequestOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: Uint8Array | string;\n json?: unknown;\n}\nexport class ManagedResponse {\n constructor(public readonly status: number,\n public readonly headers: Record<string, string[]>,\n public readonly body: Uint8Array,\n public readonly revision: number) {}\n get ok(): boolean { return this.status >= 200 && this.status < 300; }\n text(): string { return new TextDecoder().decode(this.body); }\n json<T = unknown>(): T { return JSON.parse(this.text()) as T; }\n}\nfunction encode64(bytes: Uint8Array): string {\n let binary = \"\";\n for (let start = 0; start < bytes.length; start += 8192) {\n binary += String.fromCharCode(...bytes.subarray(start, start + 8192));\n }\n return btoa(binary);\n}\nfunction decode64(value: string): Uint8Array {\n return Uint8Array.from(atob(value), char => char.charCodeAt(0));\n}\nfunction sessionPath(id: string): string {\n if (!/^[A-Za-z0-9_-]{1,64}$/.test(id)) throw new TypeError(\"Invalid session ID\");\n return `/v1/sessions/${id}`;\n}\nasync function responseText(response: Response): Promise<string> {\n const reader = response.body?.getReader();\n if (!reader) return \"\";\n const decoder = new TextDecoder();\n let size = 0;\n let text = \"\";\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n size += value.byteLength;\n if (size > 8 * 1024 * 1024) {\n await reader.cancel().catch(() => {});\n throw new GalletonError(\"invalid_response\", \"Oversized daemon response.\", response.status);\n }\n text += decoder.decode(value, { stream: true });\n }\n return text + decoder.decode();\n } finally {\n reader.releaseLock();\n }\n}\nexport class Galleton {\n private readonly base: string;\n private readonly token: string;\n private readonly timeoutMs: number;\n constructor(options: { token: string; baseURL?: string; timeoutMs?: number }) {\n const url = new URL(options.baseURL ?? \"http://127.0.0.1:8766\");\n const host = url.hostname;\n const loopback = host === \"[::1]\" ||\n /^127(?:\\.[0-9]{1,3}){3}$/.test(host) && host.split(\".\").every(n => +n <= 255);\n if (!loopback || url.protocol !== \"http:\" || url.username || url.password ||\n url.search || url.hash || url.pathname !== \"/\") {\n throw new TypeError(\"Galleton requires a literal loopback HTTP origin\");\n }\n this.base = url.origin;\n this.token = options.token.trim();\n if (!this.token || /[\\r\\n]/.test(this.token)) throw new TypeError(\"Invalid local API token\");\n this.timeoutMs = options.timeoutMs ?? 300_000;\n if (!Number.isFinite(this.timeoutMs) || this.timeoutMs <= 0) throw new TypeError(\"Invalid timeout\");\n }\n private async call<T>(method: string, path: string, body?: unknown): Promise<T> {\n let response: Response;\n try {\n response = await fetch(this.base + path, {\n method,\n headers: { Authorization: `Bearer ${this.token}`, \"Content-Type\": \"application/json\" },\n body: body === undefined ? undefined : JSON.stringify(body),\n redirect: \"manual\", signal: AbortSignal.timeout(this.timeoutMs),\n });\n } catch {\n throw new GalletonError(\"daemon_unavailable\", \"Galleton is unavailable or the local request timed out.\");\n }\n let text: string;\n try { text = await responseText(response); }\n catch (error) {\n if (error instanceof GalletonError) throw error;\n throw new GalletonError(\"daemon_unavailable\", \"Galleton response was interrupted; the request may already have completed.\", response.status);\n }\n let decoded: unknown;\n try { decoded = JSON.parse(text); }\n catch {\n if (!response.ok) throw new GalletonError(\"daemon_error\", \"Galleton request failed.\", response.status);\n throw new GalletonError(\"invalid_response\", \"Expected a JSON daemon response.\", response.status);\n }\n if (!response.ok) {\n const error = (decoded as { error?: Problem }).error;\n throw new GalletonError(error?.code ?? \"daemon_error\",\n error?.message ?? \"Galleton request failed.\", response.status, error?.retry_at);\n }\n return decoded as T;\n }\n connect(id: string, credentials: Credentials): Promise<SessionStatus> {\n return this.call(\"PUT\", sessionPath(id), credentials);\n }\n status(id: string): Promise<SessionStatus> { return this.call(\"GET\", sessionPath(id)); }\n async list(): Promise<SessionStatus[]> {\n return (await this.call<{ sessions: SessionStatus[] }>(\"GET\", \"/v1/sessions\")).sessions;\n }\n refresh(id: string): Promise<SessionStatus> { return this.call(\"POST\", sessionPath(id) + \"/refresh\", {}); }\n async headers(id: string, url: string): Promise<Record<string, string>> {\n return (await this.call<{ headers: Record<string, string> }>(\n \"POST\", sessionPath(id) + \"/headers\", { url })).headers;\n }\n capture(id: string, url: string, setCookie: string[]): Promise<SessionStatus> {\n return this.call(\"POST\", sessionPath(id) + \"/cookies\", { url, set_cookie: setCookie });\n }\n async forget(id: string): Promise<void> { await this.call(\"DELETE\", sessionPath(id)); }\n /** Buffered request with automatic Cookie/Authorization injection and Set-Cookie capture.\n * Resource calls are never automatically retried. Non-2xx upstream statuses are returned. */\n async request(id: string, url: string, options: RequestOptions = {}): Promise<ManagedResponse> {\n if (options.body !== undefined && options.json !== undefined) throw new TypeError(\"Use body or json, not both\");\n const headers = { ...options.headers };\n let body = options.body;\n if (options.json !== undefined) {\n body = JSON.stringify(options.json);\n for (const name of Object.keys(headers)) {\n if (name.toLowerCase() === \"content-type\") delete headers[name];\n }\n headers[\"Content-Type\"] = \"application/json\";\n }\n const bytes = typeof body === \"string\" ? new TextEncoder().encode(body) : body ?? new Uint8Array();\n if (bytes.length > 1024 * 1024) throw new TypeError(\"Request body exceeds 1 MiB\");\n const wire = await this.call<{\n status: number; headers: Record<string, string[]>; body_base64: string; revision: number;\n }>(\"POST\", sessionPath(id) + \"/request\", {\n url, method: options.method ?? \"GET\", headers, body_base64: encode64(bytes),\n });\n return new ManagedResponse(wire.status, wire.headers, decode64(wire.body_base64), wire.revision);\n }\n}\n","import { readFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { Config, Effect, Layer } from 'effect';\nimport { Galleton, GalletonError, type Credentials } from './vendor/galleton.js';\nimport { SessionError } from './errors.js';\nimport { Sessions } from './session-service.js';\n\nexport const origin = 'https://www.semrush.com';\nexport const defaultDaemonURL = 'http://127.0.0.1:8771';\nexport const defaultStateDirectory = join(homedir(), '.local', 'share', 'semrush-cli', 'galleton');\n\n/** Raw SDK errors stop here; daemon and upstream payloads never reach diagnostics. */\nconst sdkError = (cause: unknown): SessionError => {\n if (cause instanceof GalletonError) {\n const recovery = cause.code === 'not_found' ? 'Run semrush auth login or semrush auth import --cdp-url URL.' : 'Run semrush daemon start and semrush auth status. Reimport if renewal or authentication failed.';\n return new SessionError({ message: `Galleton ${cause.code}. ${recovery}`, code: cause.code });\n }\n return new SessionError({ message: 'Galleton operation failed. Run semrush doctor and check local configuration.' });\n};\n\nexport const makeSessionsLayer = (credentialOrigin: string) => Layer.effect(Sessions, Effect.gen(function* () {\n const baseURL = yield* Config.String('SEMRUSH_GALLETON_URL').pipe(Config.withDefault(defaultDaemonURL));\n const tokenFile = yield* Config.String('SEMRUSH_GALLETON_TOKEN_FILE').pipe(Config.withDefault(join(defaultStateDirectory, 'api.token')));\n const id = yield* Config.String('SEMRUSH_SESSION').pipe(Config.withDefault('semrush-cli'));\n const token = yield* Effect.tryPromise({ try: () => readFile(tokenFile, 'utf8'), catch: () => new SessionError({ message: 'Galleton token unavailable. Run semrush setup, or set SEMRUSH_GALLETON_TOKEN_FILE.' }) });\n const sdk = yield* Effect.try({ try: () => new Galleton({ baseURL, token, timeoutMs: 60000 }), catch: sdkError });\n const status = Effect.fn('Sessions.status')(() => Effect.tryPromise({ try: () => sdk.status(id), catch: sdkError }));\n return Sessions.of({\n status,\n headers: Effect.fn('Sessions.headers')(() => Effect.tryPromise({ try: () => sdk.headers(id, credentialOrigin + '/'), catch: sdkError })),\n save: Effect.fn('Sessions.save')(function* (credentials: Credentials) {\n // Replacements are explicit and revision-checked; missing profiles are created.\n const previous = yield* status().pipe(Effect.map(value => value.revision), Effect.catchTag('SessionError', error => error.code === 'not_found' ? Effect.succeed(undefined) : Effect.fail(error)));\n const input = { ...credentials };\n if (previous !== undefined) { input.replace = true; input.expected_revision = previous; }\n return yield* Effect.tryPromise({ try: () => sdk.connect(id, input), catch: sdkError });\n }),\n capture: Effect.fn('Sessions.capture')((cookies: string[]) => Effect.tryPromise({ try: async () => { await sdk.capture(id, credentialOrigin + '/', cookies); }, catch: sdkError })),\n forget: Effect.fn('Sessions.forget')(() => Effect.tryPromise({ try: () => sdk.forget(id), catch: sdkError })),\n });\n}));\n\nexport const sessionsLive = makeSessionsLayer(origin);\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { o as reports, r as UsageError } from "./errors-
|
|
1
|
+
import { o as reports, r as UsageError } from "./errors-DNn_XeoU.js";
|
|
2
2
|
import { Effect } from "effect";
|
|
3
3
|
import { Ajv } from "ajv";
|
|
4
4
|
|
|
@@ -14,10 +14,10 @@ function isReportName(name) {
|
|
|
14
14
|
}
|
|
15
15
|
/** Validate once at the public boundary, populate schema defaults, reject unknown keys. */
|
|
16
16
|
const parseReport = Effect.fn("Reports.parse")(function* (name, input) {
|
|
17
|
-
if (!isReportName(name)) return yield* Effect.fail(new UsageError({ message: `Unknown report: ${name}. Run semrush
|
|
17
|
+
if (!isReportName(name)) return yield* Effect.fail(new UsageError({ message: `Unknown report: ${name}. Run semrush reports.` }));
|
|
18
18
|
const validate = validators.get(name);
|
|
19
19
|
const query = structuredClone(input);
|
|
20
|
-
if (!validate || !validate(query)) return yield* Effect.fail(new UsageError({ message: `Invalid ${name} input: ${ajv.errorsText(validate?.errors)}. Run semrush
|
|
20
|
+
if (!validate || !validate(query)) return yield* Effect.fail(new UsageError({ message: `Invalid ${name} input: ${ajv.errorsText(validate?.errors)}. Run semrush ${name.replaceAll("_", "-")} --help.` }));
|
|
21
21
|
const definition = reports[name];
|
|
22
22
|
if (definition.mutation && query.confirm !== true && query.dry_run !== true) return yield* Effect.fail(new UsageError({ message: `${name} changes your Semrush account. Use --confirm (MCP confirm:true) to send it, or --dry-run to inspect the request.` }));
|
|
23
23
|
return {
|
|
@@ -29,4 +29,4 @@ const parseReport = Effect.fn("Reports.parse")(function* (name, input) {
|
|
|
29
29
|
|
|
30
30
|
//#endregion
|
|
31
31
|
export { parseReport as n, isReportName as t };
|
|
32
|
-
//# sourceMappingURL=validation-
|
|
32
|
+
//# sourceMappingURL=validation-B3hdOxdH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation-B3hdOxdH.js","names":[],"sources":["../src/validation.ts"],"sourcesContent":["import { Ajv } from 'ajv';\nimport { Effect } from 'effect';\nimport { reports, type Query, type ReportName } from './report-parameters.js';\nimport { UsageError } from './errors.js';\n\nconst ajv = new Ajv({ useDefaults: true, allErrors: true, strict: false });\nconst validators = new Map(Object.entries(reports).map(([name, report]) => [name, ajv.compile<Query>(report.schema)]));\nexport function isReportName(name: string): name is ReportName { return Object.hasOwn(reports, name); }\n\n/** Validate once at the public boundary, populate schema defaults, reject unknown keys. */\nexport const parseReport = Effect.fn('Reports.parse')(function* (name: string, input: Query) {\n if (!isReportName(name)) return yield* Effect.fail(new UsageError({ message: `Unknown report: ${name}. Run semrush reports.` }));\n const validate = validators.get(name);\n const query = structuredClone(input);\n if (!validate || !validate(query)) return yield* Effect.fail(new UsageError({ message: `Invalid ${name} input: ${ajv.errorsText(validate?.errors)}. Run semrush ${name.replaceAll('_', '-')} --help.` }));\n const definition = reports[name];\n if (definition.mutation && query.confirm !== true && query.dry_run !== true) return yield* Effect.fail(new UsageError({ message: `${name} changes your Semrush account. Use --confirm (MCP confirm:true) to send it, or --dry-run to inspect the request.` }));\n return { name, definition, query };\n});\n\nexport type ReportRequest = Effect.Success<ReturnType<typeof parseReport>>;\n"],"mappings":";;;;;AAKA,MAAM,MAAM,IAAI,IAAI;CAAE,aAAa;CAAM,WAAW;CAAM,QAAQ;CAAO,CAAC;AAC1E,MAAM,aAAa,IAAI,IAAI,OAAO,QAAQ,QAAQ,CAAC,KAAK,CAAC,MAAM,YAAY,CAAC,MAAM,IAAI,QAAe,OAAO,OAAO,CAAC,CAAC,CAAC;AACtH,SAAgB,aAAa,MAAkC;AAAE,QAAO,OAAO,OAAO,SAAS,KAAK;;;AAGpG,MAAa,cAAc,OAAO,GAAG,gBAAgB,CAAC,WAAW,MAAc,OAAc;AAC3F,KAAI,CAAC,aAAa,KAAK,CAAE,QAAO,OAAO,OAAO,KAAK,IAAI,WAAW,EAAE,SAAS,mBAAmB,KAAK,yBAAyB,CAAC,CAAC;CAChI,MAAM,WAAW,WAAW,IAAI,KAAK;CACrC,MAAM,QAAQ,gBAAgB,MAAM;AACpC,KAAI,CAAC,YAAY,CAAC,SAAS,MAAM,CAAE,QAAO,OAAO,OAAO,KAAK,IAAI,WAAW,EAAE,SAAS,WAAW,KAAK,UAAU,IAAI,WAAW,UAAU,OAAO,CAAC,gBAAgB,KAAK,WAAW,KAAK,IAAI,CAAC,WAAW,CAAC,CAAC;CACzM,MAAM,aAAa,QAAQ;AAC3B,KAAI,WAAW,YAAY,MAAM,YAAY,QAAQ,MAAM,YAAY,KAAM,QAAO,OAAO,OAAO,KAAK,IAAI,WAAW,EAAE,SAAS,GAAG,KAAK,mHAAmH,CAAC,CAAC;AAC9P,QAAO;EAAE;EAAM;EAAY;EAAO;EAClC"}
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "semrush-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Semrush research from your terminal and MCP clients. TypeScript, Effect, and Galleton.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"semrush
|
|
7
|
+
"semrush": "dist/cli.js"
|
|
8
8
|
},
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"browser-layer-DgW0vW92.js","names":["browser","payload: Schema.JsonObject | null","method","payload","options: RequestInit","extraHeaders","path","extra: Record<string, string>","reportData: Schema.Json | undefined","request"],"sources":["../src/browser-layer.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { Config, Effect, Layer, Option, Semaphore, Schema, Predicate } from 'effect';\nimport { chromium, type BrowserContext, type Page } from 'patchright';\nimport JSONbig from 'json-bigint';\nimport { Semrush } from './browser-service.js';\nimport { Sessions } from './session-service.js';\nimport { origin } from './session-layer.js';\nimport { ReportError, SessionError } from './errors.js';\nimport { reportParameters, webRequest, rpcBody, type Query } from './report-parameters.js';\nimport type { ReportRequest } from './validation.js';\n\nconst transportPaths = new Map([['domain', '/dpa/rpc'], ['direct', '/dpa/rpc'], ['magic', '/kmtgw/v2/webapi'], ['keyword', '/kwogw/v2/webapi'], ['kwm', '/kwm/rpc']]);\nconst json = JSONbig({ storeAsString: true, protoAction: 'error', constructorAction: 'error' });\nconst safeBrowserError = () => new ReportError({ message: 'Semrush browser request failed. Run semrush-cli doctor. If your session expired, sign in again with semrush-cli auth login.' });\nconst browserCall = <A>(run: () => Promise<A>) => Effect.tryPromise({ try: run, catch: safeBrowserError });\nconst objectValue = (value: Schema.Json | undefined): value is Schema.JsonObject => Predicate.isObject(value) && !Array.isArray(value);\n\n/** JSON never passes through the browser's number parser, preserving 64-bit IDs. */\nexport function parseWire(text: string): Schema.Json { return Schema.decodeUnknownSync(Schema.Json)(json.parse(text)); }\n\n/** Remove credential fields and echoed secrets, including nested provider errors. */\nexport function redact(value: Schema.Json, secrets: string[]): Schema.Json {\n if (Array.isArray(value)) return value.map(item => redact(item, secrets));\n if (objectValue(value)) return Object.fromEntries(Object.entries(value).filter(([key]) => !/^(api_?key|key|user_?id|access_token|refresh_token|authorization|cookie|set-cookie)$/i.test(key)).map(([key, item]) => [key, redact(item, secrets)]));\n if (Predicate.isString(value)) return secrets.filter(secret => secret.length > 0).reduce((text, secret) => text.replaceAll(secret, '[redacted]'), value);\n return value;\n}\n\nexport const makeSemrushLayer = (browserOrigin: string) => Layer.effect(Semrush, Effect.gen(function* () {\n const sessions = yield* Sessions;\n const executable = yield* Config.option(Config.String('SEMRUSH_BROWSER_EXECUTABLE'));\n const headers = yield* sessions.headers();\n const browser = yield* Effect.acquireRelease(browserCall(() => chromium.launch({ headless: true, executablePath: Option.getOrUndefined(executable) })), browser => Effect.promise(() => browser.close()));\n const context = yield* browserCall(() => browser.newContext({ userAgent: 'OpenAI File Downloader, XaiImageApiFetch/1.0' }));\n const cookieHeader = headers.Cookie ?? headers.cookie ?? '';\n yield* browserCall(() => context.addCookies(cookieHeader.split(';').filter(cookie => cookie.includes('=')).map(cookie => {\n const separator = cookie.indexOf('=');\n return { name: cookie.slice(0, separator).trim(), value: cookie.slice(separator + 1).trim(), url: browserOrigin, httpOnly: true, secure: browserOrigin.startsWith('https:'), sameSite: 'Lax' as const };\n })));\n const page = yield* browserCall(() => context.newPage());\n yield* browserCall(() => page.goto(browserOrigin + '/robots.txt', { waitUntil: 'domcontentloaded', timeout: 45000 }));\n const bootstrap = yield* browserCall(() => page.evaluate(async () => {\n const response = await fetch('/analytics/overview/?db=us', { credentials: 'include', signal: AbortSignal.timeout(45000) });\n return { status: response.status, text: await response.text() };\n }));\n // Extract the JSON value with a balanced scanner, never eval page-owned JavaScript.\n const auth = yield* Effect.try({ try: () => readBootstrap(bootstrap.text), catch: () => new SessionError({ message: 'Semrush sign-in metadata unavailable. Run semrush-cli auth login or import a signed-in browser.' }) });\n const semaphore = yield* Semaphore.make(1);\n let spectrumToken = '';\n const request = Effect.fn('Semrush.request')(function* (path: string, payload: Query | null, method = 'POST', extraHeaders: Record<string, string> = {}) {\n const wire = yield* browserCall(() => page.evaluate(async ({ path, encodedPayload, method, extraHeaders }) => {\n const payload: Schema.JsonObject | null = JSON.parse(encodedPayload);\n const query = new URLSearchParams();\n const get = method === 'GET';\n if (get) for (const [key, value] of Object.entries(payload ?? {})) for (const item of Array.isArray(value) ? value : [value]) query.append(key, String(item));\n const options: RequestInit = { method, credentials: 'include', headers: extraHeaders, signal: AbortSignal.timeout(45000) };\n if (!get && method !== 'DELETE' && payload !== null) { options.headers = { ...extraHeaders, 'Content-Type': 'application/json' }; options.body = JSON.stringify(payload); }\n const response = await fetch(get && query.size > 0 ? path + (path.includes('?') ? '&' : '?') + query : path, options);\n return { status: response.status, text: await response.text() };\n }, { path, encodedPayload: JSON.stringify(payload), method, extraHeaders }));\n if (wire.status < 200 || wire.status >= 300) {\n const detail = yield* Effect.try({ try: () => JSON.stringify(redact(parseWire(wire.text), [auth.apiKey, spectrumToken, spectrumToken.replace(/^Bearer /, '')])).slice(0, 500), catch: safeBrowserError }).pipe(Effect.option);\n return yield* Effect.fail(new ReportError({ message: `Semrush request returned HTTP ${wire.status}.${Option.isSome(detail) ? ' ' + detail.value : ' Check your session and account permissions.'}` }));\n }\n if (wire.status === 204 && !wire.text) return null;\n const body = yield* Effect.try({ try: () => parseWire(wire.text), catch: () => new ReportError({ message: 'Semrush returned a page or challenge instead of JSON. Sign in again or retry later.' }) });\n if (objectValue(body) && ('error' in body || (!path.startsWith('/siteaudit/') && 'status' in body && !(body.status === null || body.status === 'SUCCESS' || body.status === 'success')))) return yield* Effect.fail(new ReportError({ message: `Semrush report failed: ${JSON.stringify(redact(body.error ?? body.status ?? null, [auth.apiKey, spectrumToken, spectrumToken.replace(/^Bearer /, '')])).slice(0, 500)}` }));\n return body;\n });\n const rpc = Effect.fn('Semrush.rpc')(function* (method: string, params: Query, transport = 'keyword', webReport = 'domain.overview') {\n const payload = transport === 'domain' ? { request_id: randomUUID(), report: webReport, args: params, ...auth } : transport === 'direct' ? { ...params, ...auth } : params;\n const rpcParams = transport === 'kwm' ? { ...payload, ...(method.startsWith('user.') ? { api_key: auth.apiKey, user_id: auth.userId } : { APIKey: auth.apiKey, UserID: auth.userId }) } : payload;\n const extra: Record<string, string> = transport === 'kwm' ? { 'api-key': auth.apiKey, 'user-id': String(auth.userId) } : {};\n const body = yield* request(transportPaths.get(transport) ?? '/kwogw/v2/webapi', rpcBody(method, rpcParams), 'POST', extra);\n if (!objectValue(body) || !('result' in body)) return yield* Effect.fail(new ReportError({ message: `Semrush ${method}: missing report result.` }));\n return body.result ?? null;\n });\n const checkAuditSettings = Effect.fn('Semrush.checkAuditSettings')(function* (name: string, query: Query) {\n if (name === 'audit_create') {\n const campaigns = yield* request('/siteaudit/api/campaigns/seolist', { key: auth.apiKey }, 'GET');\n if (!Array.isArray(campaigns)) return yield* Effect.fail(new ReportError({ message: 'Site Audit campaign discovery returned an unexpected response. No settings were saved.' }));\n if (campaigns.some(campaign => objectValue(campaign) && String(campaign.id) === String(query.campaign_id))) return yield* Effect.fail(new ReportError({ message: 'This SEO project already has a Site Audit. Use audit-configure to replace its settings explicitly.' }));\n }\n // The audit save endpoint replaces settings, including protected-site credentials.\n if (name === 'audit_configure') {\n const current = yield* request(`/siteaudit/api/campaigns/${query.campaign_id}/info?key=${encodeURIComponent(auth.apiKey)}`, {}, 'GET');\n if (objectValue(current) && Predicate.isString(current.projectDomain) && current.projectDomain !== query.domain) return yield* Effect.fail(new ReportError({ message: 'The audit domain must match the SEO project website. Inspect audit-info for projectDomain before configuring it.' }));\n if (objectValue(current) && (Boolean(current.authEnabled) || Boolean(current.webBotAuthEnabled) || (objectValue(current.gaSettings) && Object.keys(current.gaSettings).length > 0))) return yield* Effect.fail(new ReportError({ message: 'This audit uses protected-site authentication or Google Analytics. Edit its settings in the Semrush web UI to preserve those connections.' }));\n }\n });\n const report = Effect.fn('Semrush.report')(function* ({ name, definition, query }: ReportRequest) {\n const valid = yield* browserCall(() => page.evaluate(async () => {\n const response = await fetch('/home/', { method: 'HEAD', credentials: 'include', cache: 'no-store', signal: AbortSignal.timeout(15000) });\n return response.ok && new URL(response.url).pathname === '/home/';\n }));\n if (!valid) return yield* Effect.fail(new SessionError({ message: 'Semrush session expired. Run semrush-cli auth login or import a signed-in browser.' }));\n const family = definition.transport;\n const planned = ['project_web', 'audit_web'].includes(family) ? webRequest(definition, query) : undefined;\n const params = planned?.body ?? reportParameters(definition, query);\n let reportData: Schema.Json | undefined;\n if (planned) {\n // The web UI only allows adding a website to a project that has none.\n if (name === 'project_update' && query.domain !== undefined) {\n const current = yield* request('/apis/v4-raw/folders/api/v0/folders/folder', { folder_id: query.folder_id ?? null }, 'GET');\n if (!objectValue(current) || !objectValue(current.folder)) return yield* Effect.fail(new ReportError({ message: 'Project metadata unavailable. Inspect the project before adding a website.' }));\n if (Predicate.isString(current.folder.domain) && current.folder.domain.length > 0) return yield* Effect.fail(new ReportError({ message: 'This project already has a website. The Semrush web UI only allows adding a website when none is set.' }));\n }\n if (family === 'audit_web') yield* checkAuditSettings(name, query);\n const path = family === 'audit_web' ? planned.path + '?key=' + encodeURIComponent(auth.apiKey) : planned.path;\n reportData = yield* request(path, planned.body, planned.method);\n } else if (['keyword', 'domain', 'direct', 'magic', 'kwm'].includes(family)) reportData = yield* rpc(definition.method, params, family, definition.web_report);\n else {\n const extra: Record<string, string> = {};\n if (family === 'gap') {\n if (!spectrumToken) spectrumToken = yield* browserCall(() => observeSpectrum(page, browserOrigin));\n Object.assign(extra, { authorization: spectrumToken, 'x-report': 'gap.keywords', 'x-request-id': randomUUID() });\n }\n let path = definition.method;\n if (family.startsWith('backlink')) {\n path = '/analytics/backlinks/webapi2/';\n params.key = auth.apiKey;\n if (family === 'backlink_overview') {\n path += 'overview/' + definition.method;\n extra['x-request-id'] = randomUUID();\n yield* request('/analytics/backlinks/webapi2/overview/init', params, 'GET', extra);\n }\n } else if (path.startsWith('/siteaudit/')) params.key = auth.apiKey;\n reportData = yield* request(path, params, ['post', 'gap', 'projects'].includes(family) ? 'POST' : 'GET', extra);\n }\n if (definition.total_method) {\n const totalParams = name === 'domain_position_changes' ? Object.fromEntries(Object.entries(params).filter(([key]) => key !== 'display')) : params;\n const total = yield* rpc(definition.total_method, totalParams, 'domain', definition.web_report);\n reportData = { rows: reportData, total, page: query.page ?? null, page_size: query.page_size ?? null };\n }\n if (name === 'keyword_overview') {\n const rows = objectValue(reportData) ? reportData.keywords : undefined;\n reportData = Array.isArray(rows) ? rows.find(item => objectValue(item) && item.database === query.database) : undefined;\n if (!reportData) return yield* Effect.fail(new ReportError({ message: 'No keyword data for the selected database.' }));\n } else if (name === 'domain_overview') {\n const search = Array.isArray(reportData) ? reportData.find(item => objectValue(item) && item.database === query.database) : undefined;\n if (!search) return yield* Effect.fail(new ReportError({ message: 'No domain data for the selected database.' }));\n reportData = { search, backlinks: yield* rpc('backlinks.Summary', { searchItem: query.domain ?? null, searchType: 'domain' }, 'domain') };\n }\n if (Predicate.isNumber(query.limit) && Array.isArray(reportData)) reportData = reportData.slice(0, query.limit);\n yield* captureCookies(context, sessions.capture, browserOrigin);\n return { authenticated: true as const, report: name, data: redact(reportData ?? null, [auth.apiKey, spectrumToken, spectrumToken.replace(/^Bearer /, '')]) };\n });\n return Semrush.of({ report: request => semaphore.withPermits(1)(report(request)) });\n}));\n\nexport const semrushLive = makeSemrushLayer(origin);\n\ninterface BootstrapAuth { readonly apiKey: string; readonly userId: string | number; }\nexport function readBootstrap(html: string): BootstrapAuth {\n const match = /window\\.sm2\\.user\\s*=\\s*/.exec(html);\n if (!match) throw new Error('No bootstrap');\n const start = match.index + match[0].length;\n let depth = 0, quoted = false, escaped = false;\n for (let index = start; index < html.length; index++) {\n const char = html[index];\n if (quoted) {\n if (escaped) escaped = false;\n else if (char === '\\\\') escaped = true;\n else if (char === '\"') quoted = false;\n } else if (char === '\"') quoted = true;\n else if (char === '{') depth++;\n else if (char === '}' && --depth === 0) {\n const user = parseWire(html.slice(start, index + 1));\n if (objectValue(user) && Predicate.isString(user.api_key) && (Predicate.isString(user.id) || Predicate.isNumber(user.id))) return { apiKey: user.api_key, userId: user.id };\n break;\n }\n }\n throw new Error('Invalid bootstrap');\n}\n\nasync function observeSpectrum(page: Page, browserOrigin: string): Promise<string> {\n try {\n // Observe and navigate together so either timeout is handled immediately.\n const [observed] = await Promise.all([\n page.waitForRequest(request => request.url().startsWith(browserOrigin + '/spectrum/') && Boolean(request.headers().authorization), { timeout: 20000 }),\n page.goto(browserOrigin + '/analytics/keywordgap/?db=us', { waitUntil: 'domcontentloaded', timeout: 45000 }),\n ]);\n return observed.headers().authorization ?? '';\n } finally {\n await page.goto(browserOrigin + '/robots.txt', { waitUntil: 'domcontentloaded', timeout: 45000 });\n }\n}\n\nconst captureCookies = Effect.fn('Semrush.captureCookies')(function* (context: BrowserContext, capture: (cookies: string[]) => Effect.Effect<void, SessionError>, cookieOrigin: string) {\n const cookies = yield* browserCall(() => context.cookies(cookieOrigin));\n yield* capture(cookies.map(cookie => `${cookie.name}=${cookie.value}; Path=${cookie.path}${cookieOrigin.startsWith('https:') ? '; Secure' : ''}${cookie.httpOnly ? '; HttpOnly' : ''}${cookie.expires > 0 ? '; Expires=' + new Date(cookie.expires * 1000).toUTCString() : ''}`));\n});\n"],"mappings":"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"}
|