mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-21 16:41:56 +00:00
refactor(cli): dedupe browser and hooks command handlers
This commit is contained in:
@@ -19,6 +19,32 @@ function runBrowserCommand(action: () => Promise<void>) {
|
||||
});
|
||||
}
|
||||
|
||||
async function runBrowserSetRequest(params: {
|
||||
parent: BrowserParentOpts;
|
||||
path: string;
|
||||
body: Record<string, unknown>;
|
||||
successMessage: string;
|
||||
}) {
|
||||
await runBrowserCommand(async () => {
|
||||
const profile = params.parent?.browserProfile;
|
||||
const result = await callBrowserRequest(
|
||||
params.parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: params.path,
|
||||
query: profile ? { profile } : undefined,
|
||||
body: params.body,
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (params.parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(params.successMessage);
|
||||
});
|
||||
}
|
||||
|
||||
export function registerBrowserStateCommands(
|
||||
browser: Command,
|
||||
parentOpts: (cmd: Command) => BrowserParentOpts,
|
||||
@@ -56,32 +82,20 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (value: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
const offline = parseOnOff(value);
|
||||
if (offline === null) {
|
||||
defaultRuntime.error(danger("Expected on|off"));
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/offline",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
offline,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`offline: ${offline}`);
|
||||
await runBrowserSetRequest({
|
||||
parent,
|
||||
path: "/set/offline",
|
||||
body: {
|
||||
offline,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: `offline: ${offline}`,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -92,7 +106,6 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const parsed = JSON.parse(String(opts.json)) as unknown;
|
||||
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
||||
@@ -104,6 +117,7 @@ export function registerBrowserStateCommands(
|
||||
headers[k] = v;
|
||||
}
|
||||
}
|
||||
const profile = parent?.browserProfile;
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
@@ -134,28 +148,16 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (username: string | undefined, password: string | undefined, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/credentials",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
username: username?.trim() || undefined,
|
||||
password,
|
||||
clear: Boolean(opts.clear),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(opts.clear ? "credentials cleared" : "credentials set");
|
||||
await runBrowserSetRequest({
|
||||
parent,
|
||||
path: "/set/credentials",
|
||||
body: {
|
||||
username: username?.trim() || undefined,
|
||||
password,
|
||||
clear: Boolean(opts.clear),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: opts.clear ? "credentials cleared" : "credentials set",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,30 +172,18 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (latitude: number | undefined, longitude: number | undefined, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/geolocation",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
latitude: Number.isFinite(latitude) ? latitude : undefined,
|
||||
longitude: Number.isFinite(longitude) ? longitude : undefined,
|
||||
accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined,
|
||||
origin: opts.origin?.trim() || undefined,
|
||||
clear: Boolean(opts.clear),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(opts.clear ? "geolocation cleared" : "geolocation set");
|
||||
await runBrowserSetRequest({
|
||||
parent,
|
||||
path: "/set/geolocation",
|
||||
body: {
|
||||
latitude: Number.isFinite(latitude) ? latitude : undefined,
|
||||
longitude: Number.isFinite(longitude) ? longitude : undefined,
|
||||
accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined,
|
||||
origin: opts.origin?.trim() || undefined,
|
||||
clear: Boolean(opts.clear),
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: opts.clear ? "geolocation cleared" : "geolocation set",
|
||||
});
|
||||
});
|
||||
|
||||
@@ -204,7 +194,6 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (value: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
const v = value.trim().toLowerCase();
|
||||
const colorScheme =
|
||||
v === "dark" ? "dark" : v === "light" ? "light" : v === "none" ? "none" : null;
|
||||
@@ -213,25 +202,14 @@ export function registerBrowserStateCommands(
|
||||
defaultRuntime.exit(1);
|
||||
return;
|
||||
}
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/media",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
colorScheme,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`media colorScheme: ${colorScheme}`);
|
||||
await runBrowserSetRequest({
|
||||
parent,
|
||||
path: "/set/media",
|
||||
body: {
|
||||
colorScheme,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: `media colorScheme: ${colorScheme}`,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -242,26 +220,14 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (timezoneId: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/timezone",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
timezoneId,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`timezone: ${timezoneId}`);
|
||||
await runBrowserSetRequest({
|
||||
parent,
|
||||
path: "/set/timezone",
|
||||
body: {
|
||||
timezoneId,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: `timezone: ${timezoneId}`,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -272,26 +238,14 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (locale: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/locale",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
locale,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`locale: ${locale}`);
|
||||
await runBrowserSetRequest({
|
||||
parent,
|
||||
path: "/set/locale",
|
||||
body: {
|
||||
locale,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: `locale: ${locale}`,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -302,26 +256,14 @@ export function registerBrowserStateCommands(
|
||||
.option("--target-id <id>", "CDP target id (or unique prefix)")
|
||||
.action(async (name: string, opts, cmd) => {
|
||||
const parent = parentOpts(cmd);
|
||||
const profile = parent?.browserProfile;
|
||||
await runBrowserCommand(async () => {
|
||||
const result = await callBrowserRequest(
|
||||
parent,
|
||||
{
|
||||
method: "POST",
|
||||
path: "/set/device",
|
||||
query: profile ? { profile } : undefined,
|
||||
body: {
|
||||
name,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
},
|
||||
{ timeoutMs: 20000 },
|
||||
);
|
||||
if (parent?.json) {
|
||||
defaultRuntime.log(JSON.stringify(result, null, 2));
|
||||
return;
|
||||
}
|
||||
defaultRuntime.log(`device: ${name}`);
|
||||
await runBrowserSetRequest({
|
||||
parent,
|
||||
path: "/set/device",
|
||||
body: {
|
||||
name,
|
||||
targetId: opts.targetId?.trim() || undefined,
|
||||
},
|
||||
successMessage: `device: ${name}`,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -152,6 +152,32 @@ function formatHookMissingSummary(hook: HookStatusEntry): string {
|
||||
return missing.join("; ");
|
||||
}
|
||||
|
||||
function exitHooksCliWithError(err: unknown): never {
|
||||
defaultRuntime.error(
|
||||
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function runHooksCliAction(action: () => Promise<void> | void): Promise<void> {
|
||||
try {
|
||||
await action();
|
||||
} catch (err) {
|
||||
exitHooksCliWithError(err);
|
||||
}
|
||||
}
|
||||
|
||||
function createInstallLogger() {
|
||||
return {
|
||||
info: (msg: string) => defaultRuntime.log(msg),
|
||||
warn: (msg: string) => defaultRuntime.log(theme.warn(msg)),
|
||||
};
|
||||
}
|
||||
|
||||
function logGatewayRestartHint() {
|
||||
defaultRuntime.log("Restart the gateway to load hooks.");
|
||||
}
|
||||
|
||||
async function readInstalledPackageVersion(dir: string): Promise<string | undefined> {
|
||||
try {
|
||||
const raw = await fsp.readFile(path.join(dir, "package.json"), "utf-8");
|
||||
@@ -469,80 +495,55 @@ export function registerHooksCli(program: Command): void {
|
||||
.option("--eligible", "Show only eligible hooks", false)
|
||||
.option("--json", "Output as JSON", false)
|
||||
.option("-v, --verbose", "Show more details including missing requirements", false)
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
.action(async (opts) =>
|
||||
runHooksCliAction(async () => {
|
||||
const config = loadConfig();
|
||||
const report = buildHooksReport(config);
|
||||
defaultRuntime.log(formatHooksList(report, opts));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(
|
||||
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
hooks
|
||||
.command("info <name>")
|
||||
.description("Show detailed information about a hook")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.action(async (name, opts) => {
|
||||
try {
|
||||
.action(async (name, opts) =>
|
||||
runHooksCliAction(async () => {
|
||||
const config = loadConfig();
|
||||
const report = buildHooksReport(config);
|
||||
defaultRuntime.log(formatHookInfo(report, name, opts));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(
|
||||
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
hooks
|
||||
.command("check")
|
||||
.description("Check hooks eligibility status")
|
||||
.option("--json", "Output as JSON", false)
|
||||
.action(async (opts) => {
|
||||
try {
|
||||
.action(async (opts) =>
|
||||
runHooksCliAction(async () => {
|
||||
const config = loadConfig();
|
||||
const report = buildHooksReport(config);
|
||||
defaultRuntime.log(formatHooksCheck(report, opts));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(
|
||||
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
hooks
|
||||
.command("enable <name>")
|
||||
.description("Enable a hook")
|
||||
.action(async (name) => {
|
||||
try {
|
||||
.action(async (name) =>
|
||||
runHooksCliAction(async () => {
|
||||
await enableHook(name);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(
|
||||
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
hooks
|
||||
.command("disable <name>")
|
||||
.description("Disable a hook")
|
||||
.action(async (name) => {
|
||||
try {
|
||||
.action(async (name) =>
|
||||
runHooksCliAction(async () => {
|
||||
await disableHook(name);
|
||||
} catch (err) {
|
||||
defaultRuntime.error(
|
||||
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
hooks
|
||||
.command("install")
|
||||
@@ -597,16 +598,13 @@ export function registerHooksCli(program: Command): void {
|
||||
|
||||
await writeConfigFile(next);
|
||||
defaultRuntime.log(`Linked hook path: ${shortenHomePath(resolved)}`);
|
||||
defaultRuntime.log(`Restart the gateway to load hooks.`);
|
||||
logGatewayRestartHint();
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await installHooksFromPath({
|
||||
path: resolved,
|
||||
logger: {
|
||||
info: (msg) => defaultRuntime.log(msg),
|
||||
warn: (msg) => defaultRuntime.log(theme.warn(msg)),
|
||||
},
|
||||
logger: createInstallLogger(),
|
||||
});
|
||||
if (!result.ok) {
|
||||
defaultRuntime.error(result.error);
|
||||
@@ -628,7 +626,7 @@ export function registerHooksCli(program: Command): void {
|
||||
|
||||
await writeConfigFile(next);
|
||||
defaultRuntime.log(`Installed hooks: ${result.hooks.join(", ")}`);
|
||||
defaultRuntime.log(`Restart the gateway to load hooks.`);
|
||||
logGatewayRestartHint();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -652,10 +650,7 @@ export function registerHooksCli(program: Command): void {
|
||||
|
||||
const result = await installHooksFromNpmSpec({
|
||||
spec: raw,
|
||||
logger: {
|
||||
info: (msg) => defaultRuntime.log(msg),
|
||||
warn: (msg) => defaultRuntime.log(theme.warn(msg)),
|
||||
},
|
||||
logger: createInstallLogger(),
|
||||
});
|
||||
if (!result.ok) {
|
||||
defaultRuntime.error(result.error);
|
||||
@@ -674,7 +669,7 @@ export function registerHooksCli(program: Command): void {
|
||||
});
|
||||
await writeConfigFile(next);
|
||||
defaultRuntime.log(`Installed hooks: ${result.hooks.join(", ")}`);
|
||||
defaultRuntime.log(`Restart the gateway to load hooks.`);
|
||||
logGatewayRestartHint();
|
||||
});
|
||||
|
||||
hooks
|
||||
@@ -726,10 +721,7 @@ export function registerHooksCli(program: Command): void {
|
||||
mode: "update",
|
||||
dryRun: true,
|
||||
expectedHookPackId: hookId,
|
||||
logger: {
|
||||
info: (msg) => defaultRuntime.log(msg),
|
||||
warn: (msg) => defaultRuntime.log(theme.warn(msg)),
|
||||
},
|
||||
logger: createInstallLogger(),
|
||||
});
|
||||
if (!probe.ok) {
|
||||
defaultRuntime.log(theme.error(`Failed to check ${hookId}: ${probe.error}`));
|
||||
@@ -750,10 +742,7 @@ export function registerHooksCli(program: Command): void {
|
||||
spec: record.spec,
|
||||
mode: "update",
|
||||
expectedHookPackId: hookId,
|
||||
logger: {
|
||||
info: (msg) => defaultRuntime.log(msg),
|
||||
warn: (msg) => defaultRuntime.log(theme.warn(msg)),
|
||||
},
|
||||
logger: createInstallLogger(),
|
||||
});
|
||||
if (!result.ok) {
|
||||
defaultRuntime.log(theme.error(`Failed to update ${hookId}: ${result.error}`));
|
||||
@@ -782,20 +771,15 @@ export function registerHooksCli(program: Command): void {
|
||||
|
||||
if (updatedCount > 0) {
|
||||
await writeConfigFile(nextCfg);
|
||||
defaultRuntime.log("Restart the gateway to load hooks.");
|
||||
logGatewayRestartHint();
|
||||
}
|
||||
});
|
||||
|
||||
hooks.action(async () => {
|
||||
try {
|
||||
hooks.action(async () =>
|
||||
runHooksCliAction(async () => {
|
||||
const config = loadConfig();
|
||||
const report = buildHooksReport(config);
|
||||
defaultRuntime.log(formatHooksList(report, {}));
|
||||
} catch (err) {
|
||||
defaultRuntime.error(
|
||||
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user