refactor(cli): dedupe browser and hooks command handlers

This commit is contained in:
Peter Steinberger
2026-02-16 17:57:20 +00:00
parent 5a39e13c92
commit 9a29d7833b
2 changed files with 147 additions and 221 deletions

View File

@@ -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( export function registerBrowserStateCommands(
browser: Command, browser: Command,
parentOpts: (cmd: Command) => BrowserParentOpts, parentOpts: (cmd: Command) => BrowserParentOpts,
@@ -56,32 +82,20 @@ export function registerBrowserStateCommands(
.option("--target-id <id>", "CDP target id (or unique prefix)") .option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (value: string, opts, cmd) => { .action(async (value: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const profile = parent?.browserProfile;
const offline = parseOnOff(value); const offline = parseOnOff(value);
if (offline === null) { if (offline === null) {
defaultRuntime.error(danger("Expected on|off")); defaultRuntime.error(danger("Expected on|off"));
defaultRuntime.exit(1); defaultRuntime.exit(1);
return; return;
} }
await runBrowserCommand(async () => { await runBrowserSetRequest({
const result = await callBrowserRequest( parent,
parent, path: "/set/offline",
{ body: {
method: "POST", offline,
path: "/set/offline", targetId: opts.targetId?.trim() || undefined,
query: profile ? { profile } : undefined, },
body: { successMessage: `offline: ${offline}`,
offline,
targetId: opts.targetId?.trim() || undefined,
},
},
{ timeoutMs: 20000 },
);
if (parent?.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
}
defaultRuntime.log(`offline: ${offline}`);
}); });
}); });
@@ -92,7 +106,6 @@ export function registerBrowserStateCommands(
.option("--target-id <id>", "CDP target id (or unique prefix)") .option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (opts, cmd) => { .action(async (opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const profile = parent?.browserProfile;
await runBrowserCommand(async () => { await runBrowserCommand(async () => {
const parsed = JSON.parse(String(opts.json)) as unknown; const parsed = JSON.parse(String(opts.json)) as unknown;
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -104,6 +117,7 @@ export function registerBrowserStateCommands(
headers[k] = v; headers[k] = v;
} }
} }
const profile = parent?.browserProfile;
const result = await callBrowserRequest( const result = await callBrowserRequest(
parent, parent,
{ {
@@ -134,28 +148,16 @@ export function registerBrowserStateCommands(
.option("--target-id <id>", "CDP target id (or unique prefix)") .option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (username: string | undefined, password: string | undefined, opts, cmd) => { .action(async (username: string | undefined, password: string | undefined, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const profile = parent?.browserProfile; await runBrowserSetRequest({
await runBrowserCommand(async () => { parent,
const result = await callBrowserRequest( path: "/set/credentials",
parent, body: {
{ username: username?.trim() || undefined,
method: "POST", password,
path: "/set/credentials", clear: Boolean(opts.clear),
query: profile ? { profile } : undefined, targetId: opts.targetId?.trim() || undefined,
body: { },
username: username?.trim() || undefined, successMessage: opts.clear ? "credentials cleared" : "credentials set",
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");
}); });
}); });
@@ -170,30 +172,18 @@ export function registerBrowserStateCommands(
.option("--target-id <id>", "CDP target id (or unique prefix)") .option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (latitude: number | undefined, longitude: number | undefined, opts, cmd) => { .action(async (latitude: number | undefined, longitude: number | undefined, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const profile = parent?.browserProfile; await runBrowserSetRequest({
await runBrowserCommand(async () => { parent,
const result = await callBrowserRequest( path: "/set/geolocation",
parent, body: {
{ latitude: Number.isFinite(latitude) ? latitude : undefined,
method: "POST", longitude: Number.isFinite(longitude) ? longitude : undefined,
path: "/set/geolocation", accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined,
query: profile ? { profile } : undefined, origin: opts.origin?.trim() || undefined,
body: { clear: Boolean(opts.clear),
latitude: Number.isFinite(latitude) ? latitude : undefined, targetId: opts.targetId?.trim() || undefined,
longitude: Number.isFinite(longitude) ? longitude : undefined, },
accuracy: Number.isFinite(opts.accuracy) ? opts.accuracy : undefined, successMessage: opts.clear ? "geolocation cleared" : "geolocation set",
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");
}); });
}); });
@@ -204,7 +194,6 @@ export function registerBrowserStateCommands(
.option("--target-id <id>", "CDP target id (or unique prefix)") .option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (value: string, opts, cmd) => { .action(async (value: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const profile = parent?.browserProfile;
const v = value.trim().toLowerCase(); const v = value.trim().toLowerCase();
const colorScheme = const colorScheme =
v === "dark" ? "dark" : v === "light" ? "light" : v === "none" ? "none" : null; v === "dark" ? "dark" : v === "light" ? "light" : v === "none" ? "none" : null;
@@ -213,25 +202,14 @@ export function registerBrowserStateCommands(
defaultRuntime.exit(1); defaultRuntime.exit(1);
return; return;
} }
await runBrowserCommand(async () => { await runBrowserSetRequest({
const result = await callBrowserRequest( parent,
parent, path: "/set/media",
{ body: {
method: "POST", colorScheme,
path: "/set/media", targetId: opts.targetId?.trim() || undefined,
query: profile ? { profile } : undefined, },
body: { successMessage: `media colorScheme: ${colorScheme}`,
colorScheme,
targetId: opts.targetId?.trim() || undefined,
},
},
{ timeoutMs: 20000 },
);
if (parent?.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
}
defaultRuntime.log(`media colorScheme: ${colorScheme}`);
}); });
}); });
@@ -242,26 +220,14 @@ export function registerBrowserStateCommands(
.option("--target-id <id>", "CDP target id (or unique prefix)") .option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (timezoneId: string, opts, cmd) => { .action(async (timezoneId: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const profile = parent?.browserProfile; await runBrowserSetRequest({
await runBrowserCommand(async () => { parent,
const result = await callBrowserRequest( path: "/set/timezone",
parent, body: {
{ timezoneId,
method: "POST", targetId: opts.targetId?.trim() || undefined,
path: "/set/timezone", },
query: profile ? { profile } : undefined, successMessage: `timezone: ${timezoneId}`,
body: {
timezoneId,
targetId: opts.targetId?.trim() || undefined,
},
},
{ timeoutMs: 20000 },
);
if (parent?.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
}
defaultRuntime.log(`timezone: ${timezoneId}`);
}); });
}); });
@@ -272,26 +238,14 @@ export function registerBrowserStateCommands(
.option("--target-id <id>", "CDP target id (or unique prefix)") .option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (locale: string, opts, cmd) => { .action(async (locale: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const profile = parent?.browserProfile; await runBrowserSetRequest({
await runBrowserCommand(async () => { parent,
const result = await callBrowserRequest( path: "/set/locale",
parent, body: {
{ locale,
method: "POST", targetId: opts.targetId?.trim() || undefined,
path: "/set/locale", },
query: profile ? { profile } : undefined, successMessage: `locale: ${locale}`,
body: {
locale,
targetId: opts.targetId?.trim() || undefined,
},
},
{ timeoutMs: 20000 },
);
if (parent?.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
}
defaultRuntime.log(`locale: ${locale}`);
}); });
}); });
@@ -302,26 +256,14 @@ export function registerBrowserStateCommands(
.option("--target-id <id>", "CDP target id (or unique prefix)") .option("--target-id <id>", "CDP target id (or unique prefix)")
.action(async (name: string, opts, cmd) => { .action(async (name: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const profile = parent?.browserProfile; await runBrowserSetRequest({
await runBrowserCommand(async () => { parent,
const result = await callBrowserRequest( path: "/set/device",
parent, body: {
{ name,
method: "POST", targetId: opts.targetId?.trim() || undefined,
path: "/set/device", },
query: profile ? { profile } : undefined, successMessage: `device: ${name}`,
body: {
name,
targetId: opts.targetId?.trim() || undefined,
},
},
{ timeoutMs: 20000 },
);
if (parent?.json) {
defaultRuntime.log(JSON.stringify(result, null, 2));
return;
}
defaultRuntime.log(`device: ${name}`);
}); });
}); });
} }

View File

@@ -152,6 +152,32 @@ function formatHookMissingSummary(hook: HookStatusEntry): string {
return missing.join("; "); 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> { async function readInstalledPackageVersion(dir: string): Promise<string | undefined> {
try { try {
const raw = await fsp.readFile(path.join(dir, "package.json"), "utf-8"); 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("--eligible", "Show only eligible hooks", false)
.option("--json", "Output as JSON", false) .option("--json", "Output as JSON", false)
.option("-v, --verbose", "Show more details including missing requirements", false) .option("-v, --verbose", "Show more details including missing requirements", false)
.action(async (opts) => { .action(async (opts) =>
try { runHooksCliAction(async () => {
const config = loadConfig(); const config = loadConfig();
const report = buildHooksReport(config); const report = buildHooksReport(config);
defaultRuntime.log(formatHooksList(report, opts)); defaultRuntime.log(formatHooksList(report, opts));
} catch (err) { }),
defaultRuntime.error( );
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
);
process.exit(1);
}
});
hooks hooks
.command("info <name>") .command("info <name>")
.description("Show detailed information about a hook") .description("Show detailed information about a hook")
.option("--json", "Output as JSON", false) .option("--json", "Output as JSON", false)
.action(async (name, opts) => { .action(async (name, opts) =>
try { runHooksCliAction(async () => {
const config = loadConfig(); const config = loadConfig();
const report = buildHooksReport(config); const report = buildHooksReport(config);
defaultRuntime.log(formatHookInfo(report, name, opts)); defaultRuntime.log(formatHookInfo(report, name, opts));
} catch (err) { }),
defaultRuntime.error( );
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
);
process.exit(1);
}
});
hooks hooks
.command("check") .command("check")
.description("Check hooks eligibility status") .description("Check hooks eligibility status")
.option("--json", "Output as JSON", false) .option("--json", "Output as JSON", false)
.action(async (opts) => { .action(async (opts) =>
try { runHooksCliAction(async () => {
const config = loadConfig(); const config = loadConfig();
const report = buildHooksReport(config); const report = buildHooksReport(config);
defaultRuntime.log(formatHooksCheck(report, opts)); defaultRuntime.log(formatHooksCheck(report, opts));
} catch (err) { }),
defaultRuntime.error( );
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
);
process.exit(1);
}
});
hooks hooks
.command("enable <name>") .command("enable <name>")
.description("Enable a hook") .description("Enable a hook")
.action(async (name) => { .action(async (name) =>
try { runHooksCliAction(async () => {
await enableHook(name); await enableHook(name);
} catch (err) { }),
defaultRuntime.error( );
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
);
process.exit(1);
}
});
hooks hooks
.command("disable <name>") .command("disable <name>")
.description("Disable a hook") .description("Disable a hook")
.action(async (name) => { .action(async (name) =>
try { runHooksCliAction(async () => {
await disableHook(name); await disableHook(name);
} catch (err) { }),
defaultRuntime.error( );
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
);
process.exit(1);
}
});
hooks hooks
.command("install") .command("install")
@@ -597,16 +598,13 @@ export function registerHooksCli(program: Command): void {
await writeConfigFile(next); await writeConfigFile(next);
defaultRuntime.log(`Linked hook path: ${shortenHomePath(resolved)}`); defaultRuntime.log(`Linked hook path: ${shortenHomePath(resolved)}`);
defaultRuntime.log(`Restart the gateway to load hooks.`); logGatewayRestartHint();
return; return;
} }
const result = await installHooksFromPath({ const result = await installHooksFromPath({
path: resolved, path: resolved,
logger: { logger: createInstallLogger(),
info: (msg) => defaultRuntime.log(msg),
warn: (msg) => defaultRuntime.log(theme.warn(msg)),
},
}); });
if (!result.ok) { if (!result.ok) {
defaultRuntime.error(result.error); defaultRuntime.error(result.error);
@@ -628,7 +626,7 @@ export function registerHooksCli(program: Command): void {
await writeConfigFile(next); await writeConfigFile(next);
defaultRuntime.log(`Installed hooks: ${result.hooks.join(", ")}`); defaultRuntime.log(`Installed hooks: ${result.hooks.join(", ")}`);
defaultRuntime.log(`Restart the gateway to load hooks.`); logGatewayRestartHint();
return; return;
} }
@@ -652,10 +650,7 @@ export function registerHooksCli(program: Command): void {
const result = await installHooksFromNpmSpec({ const result = await installHooksFromNpmSpec({
spec: raw, spec: raw,
logger: { logger: createInstallLogger(),
info: (msg) => defaultRuntime.log(msg),
warn: (msg) => defaultRuntime.log(theme.warn(msg)),
},
}); });
if (!result.ok) { if (!result.ok) {
defaultRuntime.error(result.error); defaultRuntime.error(result.error);
@@ -674,7 +669,7 @@ export function registerHooksCli(program: Command): void {
}); });
await writeConfigFile(next); await writeConfigFile(next);
defaultRuntime.log(`Installed hooks: ${result.hooks.join(", ")}`); defaultRuntime.log(`Installed hooks: ${result.hooks.join(", ")}`);
defaultRuntime.log(`Restart the gateway to load hooks.`); logGatewayRestartHint();
}); });
hooks hooks
@@ -726,10 +721,7 @@ export function registerHooksCli(program: Command): void {
mode: "update", mode: "update",
dryRun: true, dryRun: true,
expectedHookPackId: hookId, expectedHookPackId: hookId,
logger: { logger: createInstallLogger(),
info: (msg) => defaultRuntime.log(msg),
warn: (msg) => defaultRuntime.log(theme.warn(msg)),
},
}); });
if (!probe.ok) { if (!probe.ok) {
defaultRuntime.log(theme.error(`Failed to check ${hookId}: ${probe.error}`)); defaultRuntime.log(theme.error(`Failed to check ${hookId}: ${probe.error}`));
@@ -750,10 +742,7 @@ export function registerHooksCli(program: Command): void {
spec: record.spec, spec: record.spec,
mode: "update", mode: "update",
expectedHookPackId: hookId, expectedHookPackId: hookId,
logger: { logger: createInstallLogger(),
info: (msg) => defaultRuntime.log(msg),
warn: (msg) => defaultRuntime.log(theme.warn(msg)),
},
}); });
if (!result.ok) { if (!result.ok) {
defaultRuntime.log(theme.error(`Failed to update ${hookId}: ${result.error}`)); defaultRuntime.log(theme.error(`Failed to update ${hookId}: ${result.error}`));
@@ -782,20 +771,15 @@ export function registerHooksCli(program: Command): void {
if (updatedCount > 0) { if (updatedCount > 0) {
await writeConfigFile(nextCfg); await writeConfigFile(nextCfg);
defaultRuntime.log("Restart the gateway to load hooks."); logGatewayRestartHint();
} }
}); });
hooks.action(async () => { hooks.action(async () =>
try { runHooksCliAction(async () => {
const config = loadConfig(); const config = loadConfig();
const report = buildHooksReport(config); const report = buildHooksReport(config);
defaultRuntime.log(formatHooksList(report, {})); defaultRuntime.log(formatHooksList(report, {}));
} catch (err) { }),
defaultRuntime.error( );
`${theme.error("Error:")} ${err instanceof Error ? err.message : String(err)}`,
);
process.exit(1);
}
});
} }