diff --git a/src/capabilityRouting.test.ts b/src/capabilityRouting.test.ts new file mode 100644 index 0000000..3bbb01b --- /dev/null +++ b/src/capabilityRouting.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, it } from 'vitest'; +import { Strategy, type CliCommand } from './registry.js'; +import { shouldUseBrowserSession } from './capabilityRouting.js'; + +function makeCmd(partial: Partial): CliCommand { + return { + site: 'test', + name: 'command', + description: '', + args: [], + ...partial, + }; +} + +describe('shouldUseBrowserSession', () => { + it('skips browser session for public fetch-only pipelines', () => { + expect(shouldUseBrowserSession(makeCmd({ + browser: true, + strategy: Strategy.PUBLIC, + pipeline: [{ fetch: 'https://example.com/api' }, { select: 'items' }], + }))).toBe(false); + }); + + it('keeps browser session for public pipelines with browser-only steps', () => { + expect(shouldUseBrowserSession(makeCmd({ + browser: true, + strategy: Strategy.PUBLIC, + pipeline: [{ navigate: 'https://example.com' }, { evaluate: '() => []' }], + }))).toBe(true); + }); + + it('keeps browser session for non-public strategies', () => { + expect(shouldUseBrowserSession(makeCmd({ + browser: true, + strategy: Strategy.COOKIE, + pipeline: [{ fetch: 'https://example.com/api' }], + }))).toBe(true); + }); + + it('keeps browser session for function adapters', () => { + expect(shouldUseBrowserSession(makeCmd({ + browser: true, + strategy: Strategy.PUBLIC, + func: async () => [], + }))).toBe(true); + }); +}); diff --git a/src/capabilityRouting.ts b/src/capabilityRouting.ts new file mode 100644 index 0000000..c954045 --- /dev/null +++ b/src/capabilityRouting.ts @@ -0,0 +1,28 @@ +import { Strategy, type CliCommand } from './registry.js'; + +const BROWSER_ONLY_STEPS = new Set([ + 'navigate', + 'click', + 'type', + 'wait', + 'press', + 'snapshot', + 'evaluate', + 'intercept', + 'tap', +]); + +function pipelineNeedsBrowserSession(pipeline: Record[]): boolean { + return pipeline.some((step) => { + if (!step || typeof step !== 'object') return false; + return Object.keys(step).some((op) => BROWSER_ONLY_STEPS.has(op)); + }); +} + +export function shouldUseBrowserSession(cmd: CliCommand): boolean { + if (!cmd.browser) return false; + if (cmd.func) return true; + if (!cmd.pipeline || cmd.pipeline.length === 0) return true; + if (cmd.strategy !== Strategy.PUBLIC) return true; + return pipelineNeedsBrowserSession(cmd.pipeline as Record[]); +} diff --git a/src/cli.ts b/src/cli.ts index f2b8758..2f9467b 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -8,6 +8,7 @@ import { browserSession, DEFAULT_BROWSER_COMMAND_TIMEOUT, runWithTimeout } from import { PKG_VERSION } from './version.js'; import { printCompletionScript } from './completion.js'; import { CliError } from './errors.js'; +import { shouldUseBrowserSession } from './capabilityRouting.js'; export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void { const program = new Command(); @@ -158,7 +159,7 @@ export function runCli(BUILTIN_CLIS: string, USER_CLIS: string): void { try { if (actionOpts.verbose) process.env.OPENCLI_VERBOSE = '1'; let result: any; - if (cmd.browser) { + if (shouldUseBrowserSession(cmd)) { const BrowserFactory = process.env.OPENCLI_CDP_ENDPOINT ? CDPBridge : BrowserBridge; result = await browserSession(BrowserFactory as any, async (page) => { return runWithTimeout(executeCommand(cmd, page, kwargs, actionOpts.verbose), { timeout: cmd.timeoutSeconds ?? DEFAULT_BROWSER_COMMAND_TIMEOUT, label: fullName(cmd) });