/** * The `shopping` orchestrator (M12a) - the same seam `runCheck` is, one level * down. Both entrypoints (CLI, MCP) adapt over THIS; no pipeline logic lives in * `cli/` or `mcp/` (hard rule #1). * * Reuse the brand profile (M4) for category and locale, take the merchant's own * product list (there is no discovery), write two prompt layers per product, * ask the engines through the SAME `askAll` the brand check uses, score each * product, and assemble the envelope whose headline is the ranking delta. * * Everything that spends or touches the network is injected, so tests drive the * whole flow with no network (hard rule #3). Every spend goes through the cost * guard, and hitting the cap returns a partial run flagged `costCapped` rather * than throwing (hard rule #5). */ import { CostGuard } from '../costs.js'; import { type ProfileFs } from '../discovery/index.js'; import { type AskMode, type EngineAdapter } from '../engines/index.js'; import { type Fetcher } from '../fetcher/index.js'; import { type ShoppingEnvelope, type ShoppingFs } from '../shopping/index.js'; import type { JudgeClient, ProductEntity, RunSpend } from '../types.js'; import { type ConfirmContext, type ProgressEvent } from './check.js'; /** Injected collaborators. Anything that spends or hits the network lives here. */ export interface RunShoppingDeps { /** Fetcher (M2), for profile discovery; defaults to a real-network fetcher. */ fetcher?: Fetcher; /** Engine adapters (M6); their availability drives `skippedEngines`. */ adapters?: EngineAdapter[]; /** Judge for discovery, prompt writing, and the product judge pass. */ judge?: JudgeClient; /** Two-phase cost guard; defaults to uncapped. */ guard?: CostGuard; /** Profile persistence (M4). */ profileFs?: ProfileFs; /** Shopping-run persistence (M12a) - also used to read `--products-file`. */ shoppingFs?: ShoppingFs; /** Clock, injected for deterministic `generatedAt`. */ now?: () => string; /** Confirmation gate before the main spend. Skipped entirely when `yes`. */ confirm?: (ctx: ConfirmContext) => Promise; /** Env for the default adapter set, when `adapters` is not injected. */ env?: Record; /** Progress sink; defaults to a no-op. */ onProgress?: (event: ProgressEvent) => void; } /** Options mirroring the `shopping` command's flags. */ export interface RunShoppingOptions { /** State dir for the profile and saved runs (from `resolveStateDir`). */ stateDir: string; /** The merchant's products, IN THEIR RANKING ORDER. */ products?: ProductEntity[]; /** Path to a products file; when set it wins over `products`. */ productsFile?: string; refresh?: boolean; brand?: string; category?: string; samplePages?: number; mode?: AskMode; concurrency?: number; judgeRateCap?: number; /** Skip the confirmation gate (agents / CI). */ yes?: boolean; /** Persist the profile and the run (default true). */ persist?: boolean; } /** Outcome of {@link runShopping}. */ export interface RunShoppingResult { /** The shopping envelope; absent only when the run aborted at confirmation. */ envelope?: ShoppingEnvelope; /** True when the confirmation gate declined the spend (no engines asked). */ aborted: boolean; /** Path the run was saved to, if persisted. */ savedPath?: string; /** Human-readable notes (dropped products, template fallbacks, aborts). */ notes: string[]; /** What the run spent, from the cost guard. Present even on an abort. */ spend?: RunSpend; } /** * Run the `shopping` pipeline for `domain`. Never throws for a partial run - a * cost cap, a skipped engine, or a degraded profile come back on the envelope. * It DOES throw for an unusable product list, which is a usage error rather * than a partial result. */ export declare function runShopping(domain: string, deps: RunShoppingDeps | undefined, opts: RunShoppingOptions): Promise;