fix(browser-cli): rename --profile to --browser-profile to avoid conflict with global --profile flag

This commit is contained in:
James Groat
2026-01-06 09:54:31 -07:00
parent 30ea1e37f2
commit 6cebd26529
9 changed files with 127 additions and 41 deletions

View File

@@ -221,7 +221,7 @@ The agent should not assume tabs are ephemeral. It should:
## CLI quick reference (one example each) ## CLI quick reference (one example each)
All commands accept `--profile <name>` to target a specific profile (default: `clawd`). All commands accept `--browser-profile <name>` to target a specific profile (default: `clawd`).
Profile management: Profile management:
- `clawdbot browser profiles` - `clawdbot browser profiles`

View File

@@ -118,6 +118,7 @@ const BrowserToolSchema = Type.Object({
Type.Literal("dialog"), Type.Literal("dialog"),
Type.Literal("act"), Type.Literal("act"),
]), ]),
profile: Type.Optional(Type.String()),
controlUrl: Type.Optional(Type.String()), controlUrl: Type.Optional(Type.String()),
targetUrl: Type.Optional(Type.String()), targetUrl: Type.Optional(Type.String()),
targetId: Type.Optional(Type.String()), targetId: Type.Optional(Type.String()),
@@ -161,38 +162,39 @@ export function createBrowserTool(opts?: {
const params = args as Record<string, unknown>; const params = args as Record<string, unknown>;
const action = readStringParam(params, "action", { required: true }); const action = readStringParam(params, "action", { required: true });
const controlUrl = readStringParam(params, "controlUrl"); const controlUrl = readStringParam(params, "controlUrl");
const profile = readStringParam(params, "profile");
const baseUrl = resolveBrowserBaseUrl( const baseUrl = resolveBrowserBaseUrl(
controlUrl ?? opts?.defaultControlUrl, controlUrl ?? opts?.defaultControlUrl,
); );
switch (action) { switch (action) {
case "status": case "status":
return jsonResult(await browserStatus(baseUrl)); return jsonResult(await browserStatus(baseUrl, { profile }));
case "start": case "start":
await browserStart(baseUrl); await browserStart(baseUrl, { profile });
return jsonResult(await browserStatus(baseUrl)); return jsonResult(await browserStatus(baseUrl, { profile }));
case "stop": case "stop":
await browserStop(baseUrl); await browserStop(baseUrl, { profile });
return jsonResult(await browserStatus(baseUrl)); return jsonResult(await browserStatus(baseUrl, { profile }));
case "tabs": case "tabs":
return jsonResult({ tabs: await browserTabs(baseUrl) }); return jsonResult({ tabs: await browserTabs(baseUrl, { profile }) });
case "open": { case "open": {
const targetUrl = readStringParam(params, "targetUrl", { const targetUrl = readStringParam(params, "targetUrl", {
required: true, required: true,
}); });
return jsonResult(await browserOpenTab(baseUrl, targetUrl)); return jsonResult(await browserOpenTab(baseUrl, targetUrl, { profile }));
} }
case "focus": { case "focus": {
const targetId = readStringParam(params, "targetId", { const targetId = readStringParam(params, "targetId", {
required: true, required: true,
}); });
await browserFocusTab(baseUrl, targetId); await browserFocusTab(baseUrl, targetId, { profile });
return jsonResult({ ok: true }); return jsonResult({ ok: true });
} }
case "close": { case "close": {
const targetId = readStringParam(params, "targetId"); const targetId = readStringParam(params, "targetId");
if (targetId) await browserCloseTab(baseUrl, targetId); if (targetId) await browserCloseTab(baseUrl, targetId, { profile });
else await browserAct(baseUrl, { kind: "close" }); else await browserAct(baseUrl, { kind: "close" }, { profile });
return jsonResult({ ok: true }); return jsonResult({ ok: true });
} }
case "snapshot": { case "snapshot": {
@@ -212,6 +214,7 @@ export function createBrowserTool(opts?: {
format, format,
targetId, targetId,
limit, limit,
profile,
}); });
if (snapshot.format === "ai") { if (snapshot.format === "ai") {
return { return {
@@ -233,6 +236,7 @@ export function createBrowserTool(opts?: {
ref, ref,
element, element,
type, type,
profile,
}); });
return await imageResultFromFile({ return await imageResultFromFile({
label: "browser:screenshot", label: "browser:screenshot",
@@ -246,7 +250,7 @@ export function createBrowserTool(opts?: {
}); });
const targetId = readStringParam(params, "targetId"); const targetId = readStringParam(params, "targetId");
return jsonResult( return jsonResult(
await browserNavigate(baseUrl, { url: targetUrl, targetId }), await browserNavigate(baseUrl, { url: targetUrl, targetId, profile }),
); );
} }
case "console": { case "console": {
@@ -257,7 +261,7 @@ export function createBrowserTool(opts?: {
? params.targetId.trim() ? params.targetId.trim()
: undefined; : undefined;
return jsonResult( return jsonResult(
await browserConsoleMessages(baseUrl, { level, targetId }), await browserConsoleMessages(baseUrl, { level, targetId, profile }),
); );
} }
case "pdf": { case "pdf": {
@@ -265,7 +269,7 @@ export function createBrowserTool(opts?: {
typeof params.targetId === "string" typeof params.targetId === "string"
? params.targetId.trim() ? params.targetId.trim()
: undefined; : undefined;
const result = await browserPdfSave(baseUrl, { targetId }); const result = await browserPdfSave(baseUrl, { targetId, profile });
return { return {
content: [{ type: "text", text: `FILE:${result.path}` }], content: [{ type: "text", text: `FILE:${result.path}` }],
details: result, details: result,
@@ -296,6 +300,7 @@ export function createBrowserTool(opts?: {
element, element,
targetId, targetId,
timeoutMs, timeoutMs,
profile,
}), }),
); );
} }
@@ -320,6 +325,7 @@ export function createBrowserTool(opts?: {
promptText, promptText,
targetId, targetId,
timeoutMs, timeoutMs,
profile,
}), }),
); );
} }
@@ -331,6 +337,7 @@ export function createBrowserTool(opts?: {
const result = await browserAct( const result = await browserAct(
baseUrl, baseUrl,
request as Parameters<typeof browserAct>[1], request as Parameters<typeof browserAct>[1],
{ profile },
); );
return jsonResult(result); return jsonResult(result);
} }

View File

@@ -64,7 +64,7 @@ export function registerBrowserActionInputCommands(
.action(async (url: string, opts, cmd) => { .action(async (url: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserNavigate(baseUrl, { const result = await browserNavigate(baseUrl, {
url, url,
@@ -91,7 +91,7 @@ export function registerBrowserActionInputCommands(
.action(async (width: number, height: number, opts, cmd) => { .action(async (width: number, height: number, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
if (!Number.isFinite(width) || !Number.isFinite(height)) { if (!Number.isFinite(width) || !Number.isFinite(height)) {
defaultRuntime.error(danger("width and height must be numbers")); defaultRuntime.error(danger("width and height must be numbers"));
defaultRuntime.exit(1); defaultRuntime.exit(1);
@@ -130,7 +130,7 @@ export function registerBrowserActionInputCommands(
.action(async (ref: string | undefined, opts, cmd) => { .action(async (ref: string | undefined, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
const refValue = typeof ref === "string" ? ref.trim() : ""; const refValue = typeof ref === "string" ? ref.trim() : "";
if (!refValue) { if (!refValue) {
defaultRuntime.error(danger("ref is required")); defaultRuntime.error(danger("ref is required"));
@@ -179,7 +179,7 @@ export function registerBrowserActionInputCommands(
.action(async (ref: string | undefined, text: string, opts, cmd) => { .action(async (ref: string | undefined, text: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
const refValue = typeof ref === "string" ? ref.trim() : ""; const refValue = typeof ref === "string" ? ref.trim() : "";
if (!refValue) { if (!refValue) {
defaultRuntime.error(danger("ref is required")); defaultRuntime.error(danger("ref is required"));
@@ -218,7 +218,7 @@ export function registerBrowserActionInputCommands(
.action(async (key: string, opts, cmd) => { .action(async (key: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserAct( const result = await browserAct(
baseUrl, baseUrl,
@@ -248,7 +248,7 @@ export function registerBrowserActionInputCommands(
.action(async (ref: string, opts, cmd) => { .action(async (ref: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserAct( const result = await browserAct(
baseUrl, baseUrl,
@@ -279,7 +279,7 @@ export function registerBrowserActionInputCommands(
.action(async (startRef: string, endRef: string, opts, cmd) => { .action(async (startRef: string, endRef: string, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserAct( const result = await browserAct(
baseUrl, baseUrl,
@@ -311,7 +311,7 @@ export function registerBrowserActionInputCommands(
.action(async (ref: string, values: string[], opts, cmd) => { .action(async (ref: string, values: string[], opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserAct( const result = await browserAct(
baseUrl, baseUrl,
@@ -350,7 +350,7 @@ export function registerBrowserActionInputCommands(
.action(async (paths: string[], opts, cmd) => { .action(async (paths: string[], opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserArmFileChooser(baseUrl, { const result = await browserArmFileChooser(baseUrl, {
paths, paths,
@@ -383,7 +383,7 @@ export function registerBrowserActionInputCommands(
.action(async (opts, cmd) => { .action(async (opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const fields = await readFields({ const fields = await readFields({
fields: opts.fields, fields: opts.fields,
@@ -424,7 +424,7 @@ export function registerBrowserActionInputCommands(
.action(async (opts, cmd) => { .action(async (opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
const accept = opts.accept ? true : opts.dismiss ? false : undefined; const accept = opts.accept ? true : opts.dismiss ? false : undefined;
if (accept === undefined) { if (accept === undefined) {
defaultRuntime.error(danger("Specify --accept or --dismiss")); defaultRuntime.error(danger("Specify --accept or --dismiss"));
@@ -462,7 +462,7 @@ export function registerBrowserActionInputCommands(
.action(async (opts, cmd) => { .action(async (opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserAct( const result = await browserAct(
baseUrl, baseUrl,
@@ -495,7 +495,7 @@ export function registerBrowserActionInputCommands(
.action(async (opts, cmd) => { .action(async (opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
if (!opts.fn) { if (!opts.fn) {
defaultRuntime.error(danger("Missing --fn")); defaultRuntime.error(danger("Missing --fn"));
defaultRuntime.exit(1); defaultRuntime.exit(1);

View File

@@ -20,7 +20,7 @@ export function registerBrowserActionObserveCommands(
.action(async (opts, cmd) => { .action(async (opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserConsoleMessages(baseUrl, { const result = await browserConsoleMessages(baseUrl, {
level: opts.level?.trim() || undefined, level: opts.level?.trim() || undefined,
@@ -45,7 +45,7 @@ export function registerBrowserActionObserveCommands(
.action(async (opts, cmd) => { .action(async (opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserPdfSave(baseUrl, { const result = await browserPdfSave(baseUrl, {
targetId: opts.targetId?.trim() || undefined, targetId: opts.targetId?.trim() || undefined,

View File

@@ -24,7 +24,7 @@ export function registerBrowserInspectCommands(
.action(async (targetId: string | undefined, opts, cmd) => { .action(async (targetId: string | undefined, opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserScreenshotAction(baseUrl, { const result = await browserScreenshotAction(baseUrl, {
targetId: targetId?.trim() || undefined, targetId: targetId?.trim() || undefined,
@@ -59,7 +59,7 @@ export function registerBrowserInspectCommands(
.action(async (opts, cmd) => { .action(async (opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
const format = opts.format === "aria" ? "aria" : "ai"; const format = opts.format === "aria" ? "aria" : "ai";
try { try {
const result = await browserSnapshot(baseUrl, { const result = await browserSnapshot(baseUrl, {

View File

@@ -31,7 +31,7 @@ export function registerBrowserManageCommands(
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
try { try {
const status = await browserStatus(baseUrl, { const status = await browserStatus(baseUrl, {
profile: parent?.profile, profile: parent?.browserProfile,
}); });
if (parent?.json) { if (parent?.json) {
defaultRuntime.log(JSON.stringify(status, null, 2)); defaultRuntime.log(JSON.stringify(status, null, 2));
@@ -61,7 +61,7 @@ export function registerBrowserManageCommands(
.action(async (_opts, cmd) => { .action(async (_opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
await browserStart(baseUrl, { profile }); await browserStart(baseUrl, { profile });
const status = await browserStatus(baseUrl, { profile }); const status = await browserStatus(baseUrl, { profile });
@@ -85,7 +85,7 @@ export function registerBrowserManageCommands(
.action(async (_opts, cmd) => { .action(async (_opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
await browserStop(baseUrl, { profile }); await browserStop(baseUrl, { profile });
const status = await browserStatus(baseUrl, { profile }); const status = await browserStatus(baseUrl, { profile });
@@ -109,7 +109,7 @@ export function registerBrowserManageCommands(
.action(async (_opts, cmd) => { .action(async (_opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const result = await browserResetProfile(baseUrl, { profile }); const result = await browserResetProfile(baseUrl, { profile });
if (parent?.json) { if (parent?.json) {
@@ -134,7 +134,7 @@ export function registerBrowserManageCommands(
.action(async (_opts, cmd) => { .action(async (_opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const tabs = await browserTabs(baseUrl, { profile }); const tabs = await browserTabs(baseUrl, { profile });
if (parent?.json) { if (parent?.json) {
@@ -166,7 +166,7 @@ export function registerBrowserManageCommands(
.action(async (url: string, _opts, cmd) => { .action(async (url: string, _opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
const tab = await browserOpenTab(baseUrl, url, { profile }); const tab = await browserOpenTab(baseUrl, url, { profile });
if (parent?.json) { if (parent?.json) {
@@ -187,7 +187,7 @@ export function registerBrowserManageCommands(
.action(async (targetId: string, _opts, cmd) => { .action(async (targetId: string, _opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
await browserFocusTab(baseUrl, targetId, { profile }); await browserFocusTab(baseUrl, targetId, { profile });
if (parent?.json) { if (parent?.json) {
@@ -208,7 +208,7 @@ export function registerBrowserManageCommands(
.action(async (targetId: string | undefined, _opts, cmd) => { .action(async (targetId: string | undefined, _opts, cmd) => {
const parent = parentOpts(cmd); const parent = parentOpts(cmd);
const baseUrl = resolveBrowserControlUrl(parent?.url); const baseUrl = resolveBrowserControlUrl(parent?.url);
const profile = parent?.profile; const profile = parent?.browserProfile;
try { try {
if (targetId?.trim()) { if (targetId?.trim()) {
await browserCloseTab(baseUrl, targetId.trim(), { profile }); await browserCloseTab(baseUrl, targetId.trim(), { profile });

View File

@@ -1,5 +1,5 @@
export type BrowserParentOpts = { export type BrowserParentOpts = {
url?: string; url?: string;
json?: boolean; json?: boolean;
profile?: string; browserProfile?: string;
}; };

View File

@@ -0,0 +1,79 @@
import { describe, expect, it } from "vitest";
import { Command } from "commander";
describe("browser CLI --browser-profile flag", () => {
it("parses --browser-profile from parent command options", () => {
const program = new Command();
program.name("test");
const browser = program
.command("browser")
.option("--browser-profile <name>", "Browser profile name");
let capturedProfile: string | undefined;
browser.command("status").action((_opts, cmd) => {
const parent = cmd.parent?.opts?.() as { browserProfile?: string };
capturedProfile = parent?.browserProfile;
});
program.parse(["node", "test", "browser", "--browser-profile", "onasset", "status"]);
expect(capturedProfile).toBe("onasset");
});
it("defaults to undefined when --browser-profile not provided", () => {
const program = new Command();
program.name("test");
const browser = program
.command("browser")
.option("--browser-profile <name>", "Browser profile name");
let capturedProfile: string | undefined = "should-be-undefined";
browser.command("status").action((_opts, cmd) => {
const parent = cmd.parent?.opts?.() as { browserProfile?: string };
capturedProfile = parent?.browserProfile;
});
program.parse(["node", "test", "browser", "status"]);
expect(capturedProfile).toBeUndefined();
});
it("does not conflict with global --profile flag", () => {
// The global --profile flag is handled by entry.js before Commander
// This test verifies --browser-profile is a separate option
const program = new Command();
program.name("test");
program.option("--profile <name>", "Global config profile");
const browser = program
.command("browser")
.option("--browser-profile <name>", "Browser profile name");
let globalProfile: string | undefined;
let browserProfile: string | undefined;
browser.command("status").action((_opts, cmd) => {
const parent = cmd.parent?.opts?.() as { browserProfile?: string };
browserProfile = parent?.browserProfile;
globalProfile = program.opts().profile;
});
program.parse([
"node",
"test",
"--profile",
"dev",
"browser",
"--browser-profile",
"onasset",
"status",
]);
expect(globalProfile).toBe("dev");
expect(browserProfile).toBe("onasset");
});
});

View File

@@ -20,7 +20,7 @@ export function registerBrowserCli(program: Command) {
"--url <url>", "--url <url>",
"Override browser control URL (default from ~/.clawdbot/clawdbot.json)", "Override browser control URL (default from ~/.clawdbot/clawdbot.json)",
) )
.option("--profile <name>", "Browser profile name (default from config)") .option("--browser-profile <name>", "Browser profile name (default from config)")
.option("--json", "Output machine-readable JSON", false) .option("--json", "Output machine-readable JSON", false)
.addHelpText( .addHelpText(
"after", "after",