semrush-cli 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +163 -2
- package/THIRD-PARTY-NOTICES.md +19 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +1 -0
- package/dist/auth-B26NjmuZ.js +82 -0
- package/dist/auth-B26NjmuZ.js.map +1 -0
- package/dist/browser-layer-DijkeDly.js +269 -0
- package/dist/browser-layer-DijkeDly.js.map +1 -0
- package/dist/browser-service-BYYE-vSm.js +9 -0
- package/dist/browser-service-BYYE-vSm.js.map +1 -0
- package/dist/browser-service-CL8YPaIT.js +3 -0
- package/dist/cli.js +272 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon/galleton-darwin-arm64 +0 -0
- package/dist/daemon/galleton-darwin-x64 +0 -0
- package/dist/daemon/galleton-linux-arm64 +0 -0
- package/dist/daemon/galleton-linux-x64 +0 -0
- package/dist/daemon/galleton-win32-x64.exe +0 -0
- package/dist/errors-aN5dmXgv.js +2119 -0
- package/dist/errors-aN5dmXgv.js.map +1 -0
- package/dist/mcp-DUyKjMzQ.js +95 -0
- package/dist/mcp-DUyKjMzQ.js.map +1 -0
- package/dist/session-layer-u9NMfM8W.js +239 -0
- package/dist/session-layer-u9NMfM8W.js.map +1 -0
- package/dist/validation-BIbDUKRg.js +30 -0
- package/dist/validation-BIbDUKRg.js.map +1 -0
- package/dist/validation-D4jV0Q8q.js +4 -0
- package/docs/architecture.md +21 -0
- package/package.json +56 -4
- package/src/vendor/GALLETON-LICENSE +21 -0
- package/src/vendor/README.md +11 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
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";
|
|
4
|
+
import { Command, CommanderError, InvalidArgumentError, Option } from "commander";
|
|
5
|
+
import { Effect } from "effect";
|
|
6
|
+
import { chmod, mkdir, open, writeFile } from "node:fs/promises";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { execFile, spawn } from "node:child_process";
|
|
9
|
+
import { promisify } from "node:util";
|
|
10
|
+
import { setTimeout } from "node:timers/promises";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
|
|
13
|
+
//#region src/daemon.ts
|
|
14
|
+
const exec = promisify(execFile);
|
|
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." });
|
|
17
|
+
const io = (run) => Effect.tryPromise({
|
|
18
|
+
try: run,
|
|
19
|
+
catch: failed
|
|
20
|
+
});
|
|
21
|
+
/** Own daemon, state directory and port. Never edits a shared Galleton installation. */
|
|
22
|
+
const initializeDaemon = Effect.fn("Daemon.initialize")(function* () {
|
|
23
|
+
yield* io(() => mkdir(defaultStateDirectory, {
|
|
24
|
+
recursive: true,
|
|
25
|
+
mode: 448
|
|
26
|
+
}));
|
|
27
|
+
yield* io(() => chmod(defaultStateDirectory, 448));
|
|
28
|
+
yield* io(() => exec(daemonPath(), [
|
|
29
|
+
"init",
|
|
30
|
+
"--dir",
|
|
31
|
+
defaultStateDirectory
|
|
32
|
+
]));
|
|
33
|
+
const adapter = { providers: [{
|
|
34
|
+
name: "semrush",
|
|
35
|
+
kind: "static",
|
|
36
|
+
origins: [origin]
|
|
37
|
+
}] };
|
|
38
|
+
yield* io(() => writeFile(join(defaultStateDirectory, "adapters.json"), JSON.stringify(adapter, null, 2) + "\n", { mode: 384 }));
|
|
39
|
+
});
|
|
40
|
+
const startDaemon = Effect.fn("Daemon.start")(function* () {
|
|
41
|
+
if (yield* io(() => exec(daemonPath(), [
|
|
42
|
+
"list",
|
|
43
|
+
"--dir",
|
|
44
|
+
defaultStateDirectory,
|
|
45
|
+
"--api",
|
|
46
|
+
"http://127.0.0.1:8768"
|
|
47
|
+
])).pipe(Effect.match({
|
|
48
|
+
onSuccess: () => true,
|
|
49
|
+
onFailure: () => false
|
|
50
|
+
}))) return {
|
|
51
|
+
running: true,
|
|
52
|
+
url: "http://127.0.0.1:8768"
|
|
53
|
+
};
|
|
54
|
+
yield* io(async () => {
|
|
55
|
+
const log = await open(join(defaultStateDirectory, "daemon.log"), "a", 384);
|
|
56
|
+
try {
|
|
57
|
+
const child = spawn(daemonPath(), [
|
|
58
|
+
"serve",
|
|
59
|
+
"--dir",
|
|
60
|
+
defaultStateDirectory,
|
|
61
|
+
"--listen",
|
|
62
|
+
"127.0.0.1:8768"
|
|
63
|
+
], {
|
|
64
|
+
detached: true,
|
|
65
|
+
stdio: [
|
|
66
|
+
"ignore",
|
|
67
|
+
log.fd,
|
|
68
|
+
log.fd
|
|
69
|
+
]
|
|
70
|
+
});
|
|
71
|
+
await new Promise((resolve, reject) => {
|
|
72
|
+
child.once("spawn", resolve);
|
|
73
|
+
child.once("error", reject);
|
|
74
|
+
});
|
|
75
|
+
child.unref();
|
|
76
|
+
} finally {
|
|
77
|
+
await log.close();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
yield* io(async () => {
|
|
81
|
+
for (let attempt = 0; attempt < 30; attempt++) try {
|
|
82
|
+
await exec(daemonPath(), [
|
|
83
|
+
"list",
|
|
84
|
+
"--dir",
|
|
85
|
+
defaultStateDirectory,
|
|
86
|
+
"--api",
|
|
87
|
+
"http://127.0.0.1:8768"
|
|
88
|
+
]);
|
|
89
|
+
return;
|
|
90
|
+
} catch {
|
|
91
|
+
await setTimeout(100);
|
|
92
|
+
}
|
|
93
|
+
throw new Error("Daemon did not become ready");
|
|
94
|
+
});
|
|
95
|
+
return {
|
|
96
|
+
running: true,
|
|
97
|
+
url: "http://127.0.0.1:8768"
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
const stopDaemon = Effect.fn("Daemon.stop")(function* () {
|
|
101
|
+
yield* io(() => exec(daemonPath(), [
|
|
102
|
+
"shutdown",
|
|
103
|
+
"--dir",
|
|
104
|
+
defaultStateDirectory,
|
|
105
|
+
"--api",
|
|
106
|
+
"http://127.0.0.1:8768"
|
|
107
|
+
]));
|
|
108
|
+
return { stopping: true };
|
|
109
|
+
});
|
|
110
|
+
const installBrowser = Effect.fn("Browser.install")(() => io(async () => {
|
|
111
|
+
const cli$1 = fileURLToPath(new URL("./cli.js", import.meta.resolve("patchright")));
|
|
112
|
+
await exec(process.execPath, [
|
|
113
|
+
cli$1,
|
|
114
|
+
"install",
|
|
115
|
+
"chromium"
|
|
116
|
+
], {
|
|
117
|
+
timeout: 18e4,
|
|
118
|
+
maxBuffer: 1024 * 1024
|
|
119
|
+
});
|
|
120
|
+
return { installed: true };
|
|
121
|
+
}));
|
|
122
|
+
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/cli.ts
|
|
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");
|
|
128
|
+
function output(value, command) {
|
|
129
|
+
process.stdout.write(JSON.stringify(value, null, command.optsWithGlobals().json ? void 0 : 2) + "\n");
|
|
130
|
+
}
|
|
131
|
+
async function execute(program, command) {
|
|
132
|
+
await Effect.runPromise(Effect.scoped(program).pipe(Effect.match({
|
|
133
|
+
onSuccess: (value) => output(value, command),
|
|
134
|
+
onFailure: (error) => {
|
|
135
|
+
process.stderr.write(`semrush: ${error.message}\n`);
|
|
136
|
+
process.exitCode = "name" in error && error.name === "UsageError" ? 2 : 1;
|
|
137
|
+
}
|
|
138
|
+
})), { signal: interruption.signal });
|
|
139
|
+
}
|
|
140
|
+
cli.command("reports").description("List all 48 read-only research reports").action((_options, command) => {
|
|
141
|
+
output(Object.entries(reports).map(([name, report]) => ({
|
|
142
|
+
command: name.replaceAll("_", "-"),
|
|
143
|
+
tool: name,
|
|
144
|
+
description: report.description,
|
|
145
|
+
schema: report.schema
|
|
146
|
+
})), command);
|
|
147
|
+
});
|
|
148
|
+
for (const [name, definition] of Object.entries(reports)) {
|
|
149
|
+
const report = cli.command(name.replaceAll("_", "-")).description(definition.description);
|
|
150
|
+
const schema = definition.schema;
|
|
151
|
+
for (const [key, property] of Object.entries(schema.properties)) {
|
|
152
|
+
const flag = key.replaceAll("_", "-");
|
|
153
|
+
const description = (property.description ?? key.replaceAll("_", " ")) + (property.default === void 0 ? "" : ` (default: ${JSON.stringify(property.default)})`);
|
|
154
|
+
const type = property.type ?? property.anyOf?.find((candidate) => candidate.type !== "null")?.type;
|
|
155
|
+
const option = new Option(type === "boolean" ? `--${flag} [boolean]` : `--${flag} <${type === "array" || type === "object" ? "json" : "value"}>`, description);
|
|
156
|
+
option.argParser((text) => {
|
|
157
|
+
if (type === "boolean") {
|
|
158
|
+
if (!["true", "false"].includes(text)) throw new InvalidArgumentError("Expected true or false.");
|
|
159
|
+
return text === "true";
|
|
160
|
+
}
|
|
161
|
+
if (type === "integer" || type === "number") {
|
|
162
|
+
if (!text.trim() || !Number.isFinite(Number(text))) throw new InvalidArgumentError("Expected a number.");
|
|
163
|
+
return Number(text);
|
|
164
|
+
}
|
|
165
|
+
if (type === "array" || type === "object") try {
|
|
166
|
+
return JSON.parse(text);
|
|
167
|
+
} catch {
|
|
168
|
+
throw new InvalidArgumentError("Expected valid JSON.");
|
|
169
|
+
}
|
|
170
|
+
return text;
|
|
171
|
+
});
|
|
172
|
+
if (schema.required?.includes(key)) option.makeOptionMandatory();
|
|
173
|
+
report.addOption(option);
|
|
174
|
+
}
|
|
175
|
+
report.action(async (_options, command) => {
|
|
176
|
+
const { parseReport } = await import("./validation-D4jV0Q8q.js");
|
|
177
|
+
const options = command.opts();
|
|
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
|
+
const parsed = await Effect.runPromise(parseReport(name, input).pipe(Effect.match({
|
|
180
|
+
onSuccess: (value) => ({
|
|
181
|
+
ok: true,
|
|
182
|
+
value
|
|
183
|
+
}),
|
|
184
|
+
onFailure: (error) => ({
|
|
185
|
+
ok: false,
|
|
186
|
+
error
|
|
187
|
+
})
|
|
188
|
+
})));
|
|
189
|
+
if (!parsed.ok) {
|
|
190
|
+
process.stderr.write(`semrush: ${parsed.error.message}\n`);
|
|
191
|
+
process.exitCode = 2;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
const { appLayer } = await import("./mcp-DUyKjMzQ.js");
|
|
195
|
+
const { Semrush } = await import("./browser-service-CL8YPaIT.js");
|
|
196
|
+
await execute(Effect.gen(function* () {
|
|
197
|
+
return yield* (yield* Semrush).report(parsed.value);
|
|
198
|
+
}).pipe(Effect.provide(appLayer)), command);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
cli.command("setup").description("Install Chromium and initialize the private Galleton daemon").action((_options, command) => execute(Effect.gen(function* () {
|
|
202
|
+
yield* initializeDaemon();
|
|
203
|
+
yield* installBrowser();
|
|
204
|
+
return {
|
|
205
|
+
...yield* startDaemon(),
|
|
206
|
+
next: "semrush auth login"
|
|
207
|
+
};
|
|
208
|
+
}), command));
|
|
209
|
+
const daemon = cli.command("daemon").description("Control the CLI-owned Galleton daemon");
|
|
210
|
+
daemon.command("start").description("Start the private daemon").action((_options, command) => execute(startDaemon(), command));
|
|
211
|
+
daemon.command("stop").description("Gracefully stop the private daemon").action((_options, command) => execute(stopDaemon(), command));
|
|
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));
|
|
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
|
+
const status = yield* (yield* Sessions).status();
|
|
217
|
+
if (options.check) {
|
|
218
|
+
const { appLayer } = yield* Effect.promise(() => import("./mcp-DUyKjMzQ.js"));
|
|
219
|
+
const { Semrush } = yield* Effect.promise(() => import("./browser-service-CL8YPaIT.js"));
|
|
220
|
+
const { parseReport } = yield* Effect.promise(() => import("./validation-D4jV0Q8q.js"));
|
|
221
|
+
yield* Effect.gen(function* () {
|
|
222
|
+
return yield* (yield* Semrush).report(yield* parseReport("account_status", {}));
|
|
223
|
+
}).pipe(Effect.provide(appLayer));
|
|
224
|
+
}
|
|
225
|
+
return {
|
|
226
|
+
logged_in: status.status === "ready",
|
|
227
|
+
source: "galleton",
|
|
228
|
+
...status,
|
|
229
|
+
session_valid: options.check ? true : void 0
|
|
230
|
+
};
|
|
231
|
+
}).pipe(Effect.provide(sessionsLive)), command));
|
|
232
|
+
auth.command("logout").description("Forget this CLI session from Galleton").action((_options, command) => execute(Effect.gen(function* () {
|
|
233
|
+
yield* (yield* Sessions).forget();
|
|
234
|
+
return {
|
|
235
|
+
logged_in: false,
|
|
236
|
+
forgotten: true
|
|
237
|
+
};
|
|
238
|
+
}).pipe(Effect.provide(sessionsLive)), command));
|
|
239
|
+
cli.command("doctor").description("Check Galleton, browser and authenticated report access").action(async (_options, command) => {
|
|
240
|
+
const { appLayer } = await import("./mcp-DUyKjMzQ.js");
|
|
241
|
+
const { Semrush } = await import("./browser-service-CL8YPaIT.js");
|
|
242
|
+
const { parseReport } = await import("./validation-D4jV0Q8q.js");
|
|
243
|
+
await execute(Effect.gen(function* () {
|
|
244
|
+
yield* (yield* Semrush).report(yield* parseReport("account_status", {}));
|
|
245
|
+
return {
|
|
246
|
+
ok: true,
|
|
247
|
+
galleton: true,
|
|
248
|
+
chromium: true,
|
|
249
|
+
authenticated: true
|
|
250
|
+
};
|
|
251
|
+
}).pipe(Effect.provide(appLayer)), command);
|
|
252
|
+
});
|
|
253
|
+
cli.command("mcp").description("Run the read-only MCP server over stdio").action(async () => (await import("./mcp-DUyKjMzQ.js")).serveMcp());
|
|
254
|
+
process.once("SIGINT", () => {
|
|
255
|
+
if (process.argv.includes("mcp")) return;
|
|
256
|
+
process.exitCode = 130;
|
|
257
|
+
interruption.abort();
|
|
258
|
+
});
|
|
259
|
+
try {
|
|
260
|
+
await cli.parseAsync();
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (interruption.signal.aborted) process.exitCode = 130;
|
|
263
|
+
else if (error instanceof CommanderError) process.exitCode = error.exitCode === 0 ? 0 : 2;
|
|
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");
|
|
266
|
+
process.exitCode = 1;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
//#endregion
|
|
271
|
+
export { };
|
|
272
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +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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"}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|