From 615199a6a4d84dcdaec86cc03f85ccfbe92e52df Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 29 May 2026 07:29:52 -0400 Subject: [PATCH] fix(browser): centralize cli index parsing --- .../browser/src/cli/browser-cli-inspect.test.ts | 6 ++++++ extensions/browser/src/cli/browser-cli-inspect.ts | 15 +++++++++++---- .../browser/src/cli/browser-cli-manage.test.ts | 15 +++++++++++++++ extensions/browser/src/cli/browser-cli-manage.ts | 9 ++++++--- 4 files changed, 38 insertions(+), 7 deletions(-) diff --git a/extensions/browser/src/cli/browser-cli-inspect.test.ts b/extensions/browser/src/cli/browser-cli-inspect.test.ts index 3cf24e440e5..def62510ba4 100644 --- a/extensions/browser/src/cli/browser-cli-inspect.test.ts +++ b/extensions/browser/src/cli/browser-cli-inspect.test.ts @@ -179,6 +179,12 @@ describe("browser cli snapshot defaults", () => { expect(params?.query?.depth).toBe(0); }); + it("accepts signed decimal snapshot numeric options", async () => { + const params = await runSnapshot(["--limit", "+10", "--depth", "+0"]); + expect(params?.query?.limit).toBe(10); + expect(params?.query?.depth).toBe(0); + }); + it("sends screenshot request with trimmed target id and jpeg type", async () => { const params = await runBrowserInspect(["screenshot", " tab-1 ", "--type", "jpeg"], true); expect(params?.path).toBe("/screenshot"); diff --git a/extensions/browser/src/cli/browser-cli-inspect.ts b/extensions/browser/src/cli/browser-cli-inspect.ts index 8a00d01af35..090dba70990 100644 --- a/extensions/browser/src/cli/browser-cli-inspect.ts +++ b/extensions/browser/src/cli/browser-cli-inspect.ts @@ -1,7 +1,12 @@ import fs from "node:fs/promises"; import type { Command } from "commander"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; -import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js"; +import { + callBrowserRequest, + parseBrowserNonNegativeIntegerValue, + parseBrowserPositiveIntegerValue, + type BrowserParentOpts, +} from "./browser-cli-shared.js"; import { danger, defaultRuntime, @@ -18,9 +23,11 @@ function parseOptionalIntegerOption( if (value === undefined) { return undefined; } - const raw = value.trim(); - const parsed = /^\d+$/.test(raw) ? Number(raw) : Number.NaN; - if (!Number.isSafeInteger(parsed) || parsed < opts.min) { + const parsed = + opts.min === 0 + ? parseBrowserNonNegativeIntegerValue(value) + : parseBrowserPositiveIntegerValue(value); + if (parsed === undefined || parsed < opts.min) { defaultRuntime.error(danger(`Invalid ${label}: must be an integer >= ${opts.min}`)); defaultRuntime.exit(1); return undefined; diff --git a/extensions/browser/src/cli/browser-cli-manage.test.ts b/extensions/browser/src/cli/browser-cli-manage.test.ts index 1faedfd5022..002bdf50162 100644 --- a/extensions/browser/src/cli/browser-cli-manage.test.ts +++ b/extensions/browser/src/cli/browser-cli-manage.test.ts @@ -285,6 +285,21 @@ describe("browser manage output", () => { ); }); + it("accepts signed decimal tab indexes", async () => { + const program = createBrowserManageProgram(); + + await program.parseAsync(["browser", "tab", "select", "+2"], { from: "user" }); + + expect(getBrowserManageCallBrowserRequestMock()).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + path: "/tabs/action", + body: { action: "select", index: 1 }, + }), + expect.anything(), + ); + }); + it("prints a readable browser doctor report", async () => { getBrowserManageCallBrowserRequestMock().mockImplementation(async (_opts: unknown, req) => { if (req.path === "/") { diff --git a/extensions/browser/src/cli/browser-cli-manage.ts b/extensions/browser/src/cli/browser-cli-manage.ts index 62933aaa002..b620e25965f 100644 --- a/extensions/browser/src/cli/browser-cli-manage.ts +++ b/extensions/browser/src/cli/browser-cli-manage.ts @@ -1,6 +1,10 @@ import type { Command } from "commander"; import { runCommandWithRuntime } from "../core-api.js"; -import { callBrowserRequest, type BrowserParentOpts } from "./browser-cli-shared.js"; +import { + callBrowserRequest, + parseBrowserPositiveIntegerValue, + type BrowserParentOpts, +} from "./browser-cli-shared.js"; import { danger, defaultRuntime, @@ -113,8 +117,7 @@ function runBrowserCommand(action: () => Promise) { } function parseTabIndex(value: string): number { - const trimmed = value.trim(); - return /^\d+$/.test(trimmed) ? Number(trimmed) : Number.NaN; + return parseBrowserPositiveIntegerValue(value) ?? Number.NaN; } function logBrowserTabs(tabs: BrowserTab[], json?: boolean) {