@bowmark/web 1.6.0 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,9 @@
5
5
  // rather than imported. An `import` or `export` at the top level of this file would
6
6
  // turn it into a module and every declaration below would stop being global.
7
7
  //
8
- // Manifest version: 5426792a92faf41fd663aa77ef068ec2f38bcc28d33426a6a565458c7a3b4c42
9
- // 8 capabilities, 95 providers, 316 typed functions, 20 refused.
10
- // 51,713 family members, sharing 2 interface(s) — declared once and pointed at, never repeated per member.
8
+ // Manifest version: a400421498212bf60bfaab4808ff698bd8edb4febae1ba86fda5cb9ebc232c1b
9
+ // 9 capabilities, 109 providers, 356 typed functions, 20 refused.
10
+ // 51,714 family members, sharing 2 interface(s) — declared once and pointed at, never repeated per member.
11
11
  //
12
12
  // REFUSED — these functions are real and callable, and their declared arguments
13
13
  // carry no types, so no honest signature exists. Each one is commented in place
@@ -861,6 +861,126 @@ type ReadResult = {
861
861
  }
862
862
  }
863
863
 
864
+ declare namespace BowmarkCapability_sheds {
865
+ // ── Sheds and portable buildings (configure and price) — the unit's own declarations, verbatim ──
866
+ type ShedSize = {
867
+ sizeKey: string // the maker's own key; opaque, meaningful only to source
868
+ widthFt: number // FEET, always — the maker's own units are converted away
869
+ lengthFt: number
870
+ }
871
+ type ShedStyle = {
872
+ source: string // which maker — a provider id
873
+ brand: string // the maker's brand name, for an answer that names who builds it
874
+ key: string // the maker's own style key
875
+ label: string // the style as a customer sees it ("Lofted Barn")
876
+ sidingOptions: string[] // siding keys; the first is the maker's standard
877
+ sizes: ShedSize[]
878
+ imageUrl: string | null // a real product image from the maker's own catalogue
879
+ roofStyle: string | null // "gable", "gambrel", ...
880
+ roofing: string | null // "metal", ...
881
+ wallHeight: string | null // the maker's own spec, verbatim, e.g. "left-78-right-78-eave-72"
882
+ }
883
+ type ShedStylesResult = {
884
+ styles: ShedStyle[]
885
+ warnings: string[] // always present; a maker named here returned NOTHING
886
+ }
887
+
888
+ type ShedQuoteRequest = {
889
+ widthFt: number // in feet, as a person says it — "a 12 by 20"
890
+ lengthFt: number
891
+ zip: string // required; every maker prices regionally
892
+ style?: string // style key OR its customer-facing name, matched loosely.
893
+ // Omit to price EVERY style that builds the size
894
+ siding?: string // omit for the maker's standard siding
895
+ }
896
+ type ShedQuote = {
897
+ source: string
898
+ brand: string
899
+ styleKey: string
900
+ style: string // "Lofted Barn"
901
+ model: string | null // the maker's model name, where it has one
902
+ widthFt: number
903
+ lengthFt: number
904
+ siding: string // the siding key this was priced in
905
+ zip: string
906
+ region: string // the maker's own pricing region that zip fell under
907
+ basePrice: number
908
+ sidingSurcharge: number // what the siding added, by the maker's rules; 0 for standard
909
+ total: number // basePrice + sidingSurcharge, the maker's own arithmetic
910
+ currency: string
911
+ imageUrl: string | null // what the building looks like, from the maker's catalogue
912
+ roofStyle: string | null // "gable", "gambrel", ...
913
+ roofing: string | null // "metal", ...
914
+ wallHeight: string | null // the maker's own spec, verbatim
915
+ orderUrl: string // where to go order THIS build, always present
916
+ }
917
+ type ShedQuoteResult = {
918
+ quotes: ShedQuote[] // cheapest first, across every maker that builds the size
919
+ warnings: string[] // always present; names a dropped maker, a style that does
920
+ // not build the size, and a capped fan
921
+ }
922
+
923
+ type ShedDealer = {
924
+ source: string
925
+ brand: string
926
+ name: string
927
+ city: string
928
+ state: string
929
+ zip: string
930
+ phone: string | null // null when the directory lists none (never "")
931
+ url: string
932
+ }
933
+ type ShedDealerResult = {
934
+ dealers: ShedDealer[]
935
+ warnings: string[]
936
+ }
937
+
938
+ type CallOptions = {
939
+ timeoutMs?: number // per-provider budget in ms, default 30000, clamped to 1000-55000.
940
+ // A provider slower than this is DROPPED from the results and
941
+ // NAMED in warnings — never silently absent
942
+ }
943
+
944
+ /**
945
+ * Price a portable building the way its maker's own 3D configurator does — give a size in feet
946
+ * and a zip and get the real regional price for every style that builds it, the exact siding
947
+ * surcharge rather than a guessed range, a real product image and spec for each one, and the
948
+ * link to go order it. Plus the maker's real dealer locations by state.
949
+ */
950
+ interface Unit {
951
+ /**
952
+ * Prices a building at a real size for a real zip, exactly the way the maker's own
953
+ * configurator does — the regional base price plus the EXACT siding surcharge its rules apply
954
+ * at that width and region, never a national range or a guessed upcharge. Sizes are in FEET ({
955
+ * widthFt: 12, lengthFt: 20 }), and both orientations count. Omit `style` to price every style
956
+ * that builds the size, cheapest first; name it loosely ("lofted barn") when you want one.
957
+ * `zip` is required because every maker prices regionally. Each quote DESCRIBES the building
958
+ * as well as costing it — a real product image, the roof line and roofing material, the
959
+ * maker's own wall-height spec — and carries `orderUrl`, their own page to go order that
960
+ * build. `warnings` is always present and names a maker that failed, styles that do not build
961
+ * the size, and a capped fan. `options.timeoutMs` sets the per-maker budget (default 30000).
962
+ */
963
+ quote(request: ShedQuoteRequest, options?: CallOptions): Promise<ShedQuoteResult>;
964
+
965
+ /**
966
+ * Lists every building style each maker actually offers — its customer-facing name, the siding
967
+ * it can be built in, every real buildable size in FEET, and what the building IS (a real
968
+ * product image, the roof line and roofing material, the maker's own wall-height spec). Use it
969
+ * to see what exists before pricing, or to answer "what sizes do they even make". `warnings`
970
+ * names any maker that returned nothing, which is not the same as a maker with no styles.
971
+ */
972
+ listStyles(options?: CallOptions): Promise<ShedStylesResult>;
973
+
974
+ /**
975
+ * Looks up the real places that sell a maker's buildings in one US state or Canadian province
976
+ * — full name ("Tennessee") or abbreviation ("TN") — with name, city, phone and the dealer's
977
+ * own page, for handing a priced configuration to somebody who can actually build it. `phone`
978
+ * is null when the directory lists none, never an empty string.
979
+ */
980
+ findDealers(state: string, options?: CallOptions): Promise<ShedDealerResult>;
981
+ }
982
+ }
983
+
864
984
  declare namespace BowmarkProvider_aa {
865
985
  // ── American Airlines — the unit's own declarations, verbatim ──
866
986
  interface aaFlight {
@@ -1318,6 +1438,94 @@ interface abercrombieStockQuery {
1318
1438
  }
1319
1439
  }
1320
1440
 
1441
+ declare namespace BowmarkProvider_aiper {
1442
+ // ── Aiper — the unit's own declarations, verbatim ──
1443
+ interface AiperPoolOption {
1444
+ id: string;
1445
+ label: string;
1446
+ }
1447
+ interface AiperPoolQuestion {
1448
+ id: string;
1449
+ problemName: string;
1450
+ multiSelect: boolean;
1451
+ options: AiperPoolOption[];
1452
+ }
1453
+ interface AiperPoolAnswerInput {
1454
+ question: string;
1455
+ choice: string | string[];
1456
+ }
1457
+ interface AiperRecommendedProduct {
1458
+ productId: string;
1459
+ slug: string;
1460
+ name: string;
1461
+ sku: string;
1462
+ price: number;
1463
+ regularPrice: number;
1464
+ url: string;
1465
+ image: string | null;
1466
+ }
1467
+ interface AiperPoolRecommendation {
1468
+ products: AiperRecommendedProduct[];
1469
+ answers: AiperPoolAnswerInput[];
1470
+ warnings: string[];
1471
+ }
1472
+
1473
+ /**
1474
+ * Aiper's Help Me Choose robotic-pool-cleaner finder, run for real — the quiz's own computed
1475
+ * recommendation (model, SKU, real current price, PDP link) for a buyer's pool answers, off
1476
+ * the site's own undocumented API.
1477
+ */
1478
+ interface Unit {
1479
+ /**
1480
+ * Reads the Help Me Choose quiz's live question list — every question in order, with its
1481
+ * option ids and labels. The entry point: recommendPoolCleaner takes answers keyed off these
1482
+ * labels, so a caller normally reads this first (or already knows the labels from a prior
1483
+ * call).
1484
+ */
1485
+ listPoolChooserQuestions(): Promise<AiperPoolQuestion[]>;
1486
+
1487
+ /**
1488
+ * Runs the Help Me Choose quiz's real backend computation for a buyer's answers (given as
1489
+ * question/choice LABELS, matched case-insensitively against listPoolChooserQuestions) and
1490
+ * returns the same computed recommendation the quiz's own terminal page renders: model name,
1491
+ * SKU, real current price, list price, and a PDP URL. THROWS if a question or choice label
1492
+ * doesn't match, or if the site's computed result carries no product list.
1493
+ */
1494
+ recommendPoolCleaner(answers: AiperPoolAnswerInput[]): Promise<AiperPoolRecommendation>;
1495
+ }
1496
+ }
1497
+
1498
+ declare namespace BowmarkProvider_ajmadison {
1499
+ // ── AJ Madison — the unit's own declarations, verbatim ──
1500
+ interface AjmadisonSearchArgs {
1501
+ category: string; // the site's own category slug, e.g. "refrigerators"
1502
+ filters?: Record<string, string>; // the site's own facet query params, verbatim
1503
+ limit?: number; // default 25, clamped to [1, 60]
1504
+ }
1505
+
1506
+ interface AjmadisonSearchResult {
1507
+ sku: string;
1508
+ name: string;
1509
+ price: number; // real, current selling price
1510
+ wasPrice: number | null; // the crossed-out "was" price, when shown
1511
+ url: string; // this product's own AJ Madison URL
1512
+ }
1513
+
1514
+ /**
1515
+ * AJ Madison's real appliance catalog — search runs the site's own category + facet filter
1516
+ * (brand, size/capacity, price band, style, availability) and returns real, currently-listed
1517
+ * products with their live selling price and the product's own AJ Madison URL.
1518
+ */
1519
+ interface Unit {
1520
+ /**
1521
+ * Runs AJ Madison's own category + facet filter and returns real, currently-listed products
1522
+ * (name, real current price, the crossed-out 'was' price when shown, the product's own AJ
1523
+ * Madison URL). Read-only — never adds to cart or checks out.
1524
+ */
1525
+ search(args: AjmadisonSearchArgs): Promise<AjmadisonSearchResult[]>;
1526
+ }
1527
+ }
1528
+
1321
1529
  declare namespace BowmarkProvider_ashleyfurniture {
1322
1530
  // ── Ashley Furniture — the unit's own declarations, verbatim ──
1323
1531
  interface AshleyFurnitureSearchArgs {
@@ -1438,6 +1646,47 @@ interface AshleyFurnitureStore {
1438
1646
  }
1439
1647
  }
1440
1648
 
1649
+ declare namespace BowmarkProvider_atlasseniorliving {
1650
+ // ── Atlas Senior Living — the unit's own declarations, verbatim ──
1651
+ type AtlasCareType = "assisted_living" | "independent_living" | "memory_care" | "respite_care";
1652
+
1653
+ interface AtlasCommunity {
1654
+ id: string;
1655
+ name: string;
1656
+ address: string;
1657
+ city: string;
1658
+ state: string;
1659
+ zip: string;
1660
+ phone: string;
1661
+ distanceMiles: number;
1662
+ lat: number;
1663
+ lng: number;
1664
+ url: string;
1665
+ }
1666
+
1667
+ interface AtlasSearchCommunitiesResult {
1668
+ location: string;
1669
+ geocodedLat: number;
1670
+ geocodedLng: number;
1671
+ careTypes: AtlasCareType[];
1672
+ radiusMiles: number;
1673
+ communities: AtlasCommunity[];
1674
+ }
1675
+
1676
+ /**
1677
+ * Atlas Senior Living's own community search (atlasseniorliving.com/our-communities/) — given
1678
+ * a US city/state or ZIP, plus optional care type(s) and radius, returns the real,
1679
+ * distance-sorted set of matching Atlas communities.
1680
+ */
1681
+ interface Unit {
1682
+ /**
1683
+ * Runs the site's own community search: a US location + optional care type(s) and radius,
1684
+ * returns the real, distance-sorted matching Atlas communities.
1685
+ */
1686
+ searchCommunities(arg: { location: string, careTypes?: AtlasCareType[], radiusMiles?: number, maxResults?: number }): Promise<AtlasSearchCommunitiesResult>;
1687
+ }
1688
+ }
1689
+
1441
1690
  declare namespace BowmarkProvider_avis {
1442
1691
  // ── Avis — the unit's own declarations, verbatim ──
1443
1692
  interface avisRow {
@@ -2711,6 +2960,75 @@ interface ChriscraftPriceResult {
2711
2960
  }
2712
2961
  }
2713
2962
 
2963
+ declare namespace BowmarkProvider_classichome {
2964
+ // ── Classic Home — the unit's own declarations, verbatim ──
2965
+ // Classic Home's OWN shapes — not a capability contract.
2966
+
2967
+ interface ClassicHomeProduct {
2968
+ handle: string; // the key getProduct/addToCart take
2969
+ title: string;
2970
+ url: string;
2971
+ priceMin: number; // dollars — the cheapest real fabric/leather choice
2972
+ priceMax: number; // dollars — the most expensive (usually top-grain leather)
2973
+ variantCount: number; // >1 means a real fabric/leather choice exists
2974
+ }
2975
+
2976
+ interface ClassicHomeVariant {
2977
+ optionValue: string; // e.g. "Soft Olive", "Dawn-Flax", "ElPaso-Saddle"
2978
+ sku: string;
2979
+ price: number; // dollars — this exact fabric/leather's own real price
2980
+ available: boolean;
2981
+ }
2982
+
2983
+ interface ClassicHomeProductDetail {
2984
+ handle: string;
2985
+ title: string;
2986
+ url: string;
2987
+ optionName: string; // usually "Color" (fabric/leather); "Title" if no real picker
2988
+ variants: ClassicHomeVariant[];
2989
+ }
2990
+
2991
+ interface ClassicHomeCartHandoff {
2992
+ handle: string;
2993
+ optionValue: string;
2994
+ sku: string;
2995
+ price: number; // dollars — the real price for this exact fabric/leather
2996
+ available: boolean;
2997
+ productUrl: string; // finish add-to-cart/checkout on the real product page
2998
+ }
2999
+
3000
+ /**
3001
+ * Classic Home's real Made-to-Order fabric/leather catalog and its real, material-specific
3002
+ * Shopify pricing — search real MTO products (sofas, chairs, ottomans), read one product's
3003
+ * real fabric/leather picker, and resolve one exact fabric or leather choice to its real SKU,
3004
+ * price and availability.
3005
+ */
3006
+ interface Unit {
3007
+ /**
3008
+ * Searches Classic Home's real Made-to-Order catalog (sofas, chairs, ottomans) via the site's
3009
+ * own Shopify collection JSON, optionally filtered by a free-text query against the product
3010
+ * title. Returns real products with a real price range read off their own fabric/leather
3011
+ * variants.
3012
+ */
3013
+ searchProducts(query?: string): Promise<ClassicHomeProduct[]>;
3014
+
3015
+ /**
3016
+ * Reads one product's real live fabric/leather picker: every real color/material choice with
3017
+ * its own real price and availability, keyed by the site's own option name. THROWS on an
3018
+ * unknown handle, naming searchProducts() as the way to find current ones.
3019
+ */
3020
+ getProduct(handle: string): Promise<ClassicHomeProductDetail>;
3021
+
3022
+ /**
3023
+ * Resolves ONE exact fabric/leather choice (optionValue from getProduct's own variant list,
3024
+ * e.g. "Soft Olive") to Classic Home's own real price, availability and SKU, plus the product
3025
+ * page to finish add-to-cart/checkout on the site itself. Writes nothing — classichome.com's
3026
+ * robots.txt disallows automated /cart access.
3027
+ */
3028
+ addToCart(handle: string, optionValue: string): Promise<ClassicHomeCartHandoff>;
3029
+ }
3030
+ }
3031
+
2714
3032
  declare namespace BowmarkProvider_classpass {
2715
3033
  // ── ClassPass — the unit's own declarations, verbatim ──
2716
3034
  /** Everything /v2/venues publishes about one studio — a superset of
@@ -3686,6 +4004,36 @@ interface DiscounttireTireSizeSearch {
3686
4004
  }
3687
4005
  }
3688
4006
 
4007
+ declare namespace BowmarkProvider_embroker {
4008
+ // ── Embroker — the unit's own declarations, verbatim ──
4009
+ interface EmbrokerCoverageCatalog {
4010
+ coverageLines: string[];
4011
+ productTypes: string[];
4012
+ }
4013
+
4014
+ interface EmbrokerQuoteEntryPoint {
4015
+ product: string;
4016
+ productLabel: string;
4017
+ url: string;
4018
+ reachable: boolean;
4019
+ }
4020
+
4021
+ /**
4022
+ * Embroker's own coverage catalog and live self-serve quote-wizard entry points for
4023
+ * tech/startup, law firm, cyber, BOP, crime and professional-liability business insurance.
4024
+ */
4025
+ interface Unit {
4026
+ /** Returns Embroker's real coverage-line and policy-product-type catalog. */
4027
+ listCoverageLines(): Promise<EmbrokerCoverageCatalog>;
4028
+
4029
+ /**
4030
+ * Returns the live, confirmed-reachable entry URL for one of Embroker's self-serve
4031
+ * quote-wizard products.
4032
+ */
4033
+ getQuoteEntryPoint(args: { product: string }): Promise<EmbrokerQuoteEntryPoint>;
4034
+ }
4035
+ }
4036
+
3689
4037
  declare namespace BowmarkProvider_erieinsurance {
3690
4038
  // ── ERIE Insurance — the unit's own declarations, verbatim ──
3691
4039
  interface ErieAgentQuery {
@@ -5113,6 +5461,53 @@ interface GooglePriceGraph {
5113
5461
  }
5114
5462
  }
5115
5463
 
5464
+ declare namespace BowmarkProvider_gotchacovered {
5465
+ // ── Gotcha Covered — the unit's own declarations, verbatim ──
5466
+ interface GotchaCoveredQuizOption {
5467
+ label: string;
5468
+ imageUrl: string | null;
5469
+ }
5470
+ interface GotchaCoveredQuizQuestion {
5471
+ step: number;
5472
+ prompt: string;
5473
+ options: GotchaCoveredQuizOption[];
5474
+ }
5475
+ interface GotchaCoveredQuizQuestions {
5476
+ questions: GotchaCoveredQuizQuestion[];
5477
+ }
5478
+ interface GotchaCoveredQuizAnswers {
5479
+ colorScheme: string;
5480
+ pattern: string;
5481
+ imageChoice: number | string;
5482
+ destination: string;
5483
+ material: string;
5484
+ item: string;
5485
+ }
5486
+ interface GotchaCoveredQuizResult {
5487
+ styleName: string;
5488
+ description: string;
5489
+ recommendedProducts: string | null;
5490
+ url: string;
5491
+ }
5492
+
5493
+ /**
5494
+ * Reads and answers Gotcha Covered's own 'What Design Style Am I?' window-treatment style
5495
+ * quiz, returning the site's real computed match.
5496
+ */
5497
+ interface Unit {
5498
+ /** Reads the live 'What Design Style Am I?' quiz's real 6 questions and option lists. */
5499
+ getDesignStyleQuizQuestions(): Promise<GotchaCoveredQuizQuestions>;
5500
+
5501
+ /**
5502
+ * Answers all 6 questions of Gotcha Covered's Design Style Quiz (e.g. { colorScheme:
5503
+ * "Metallics Color Scheme", pattern: "Eclectic Pattern", imageChoice: 1, destination: "Spain
5504
+ * Destination", material: "Wood Material", item: "Graphic Rug Item" }) and returns the site's
5505
+ * real computed style match.
5506
+ */
5507
+ takeDesignStyleQuiz(answers: GotchaCoveredQuizAnswers): Promise<GotchaCoveredQuizResult>;
5508
+ }
5509
+ }
5510
+
5116
5511
  declare namespace BowmarkProvider_grainger {
5117
5512
  // ── Grainger — the unit's own declarations, verbatim ──
5118
5513
  interface graingerRow {
@@ -5219,6 +5614,132 @@ interface graingerStockRow {
5219
5614
  }
5220
5615
  }
5221
5616
 
5617
+ declare namespace BowmarkProvider_handypro {
5618
+ // ── HandyPro — the unit's own declarations, verbatim ──
5619
+ // HandyPro's OWN shapes — not a capability contract.
5620
+
5621
+ interface HandyproHourlyRate { fromHours: number; toHours: number; pricePerHour: number }
5622
+
5623
+ interface HandyproServiceArea {
5624
+ covered: boolean;
5625
+ message?: string; // present only when covered is false
5626
+ franchiseeName?: string;
5627
+ phone?: string;
5628
+ city?: string;
5629
+ state?: string;
5630
+ formattedAddress?: string;
5631
+ hourlyRates?: HandyproHourlyRate[];
5632
+ }
5633
+
5634
+ interface HandyproCategoryPrice {
5635
+ categoryId: string;
5636
+ categoryName: string;
5637
+ parentCategoryName: string;
5638
+ pricingKind: "estimate" | "fixedJob" | "unpriced";
5639
+ price: number | null; // dollars
5640
+ whatsIncluded: string;
5641
+ }
5642
+
5643
+ interface HandyproCategorySearch {
5644
+ zipcode: string;
5645
+ covered: boolean;
5646
+ message?: string; // present only when covered is false
5647
+ categories: HandyproCategoryPrice[];
5648
+ }
5649
+
5650
+ /**
5651
+ * HandyPro's real service-area coverage and per-category job pricing — checks whether a ZIP is
5652
+ * served by a real local franchisee (with its own live hourly rate table) and prices
5653
+ * HandyPro's actual handyman/home-modification categories (grab bars, appliance install, TV
5654
+ * mounting, painting, and more) for that ZIP, rather than a researched nationwide estimate.
5655
+ */
5656
+ interface Unit {
5657
+ /**
5658
+ * Checks whether a ZIP is served by a real local HandyPro franchisee and returns that
5659
+ * franchisee's own real hourly rate table. covered is false with a message for a ZIP outside
5660
+ * HandyPro's online booking area — an honest, ordinary answer, not an error.
5661
+ */
5662
+ checkServiceArea(zipcode: string): Promise<HandyproServiceArea>;
5663
+
5664
+ /**
5665
+ * Lists HandyPro's real service categories priced for one ZIP's franchisee (a free estimate or
5666
+ * a real fixed job price, and what's included), optionally narrowed by a free-text query (e.g.
5667
+ * "grab bar"). covered is false with a message for a ZIP outside the service area.
5668
+ */
5669
+ searchServiceCategories(zipcode: string, query?: string): Promise<HandyproCategorySearch>;
5670
+ }
5671
+ }
5672
+
5673
+ declare namespace BowmarkProvider_harmar {
5674
+ // ── Harmar Mobility — the unit's own declarations, verbatim ──
5675
+ interface HarmarVehicleModel {
5676
+ make: string;
5677
+ modelId: string;
5678
+ model: string;
5679
+ }
5680
+
5681
+ interface HarmarChairModel {
5682
+ chairId: string;
5683
+ model: string;
5684
+ }
5685
+
5686
+ interface HarmarLiftOption {
5687
+ code: string;
5688
+ name: string;
5689
+ description: string;
5690
+ required: boolean;
5691
+ }
5692
+
5693
+ interface HarmarCompatibleLift {
5694
+ liftId: string;
5695
+ productCode: string;
5696
+ name: string;
5697
+ requiredAccessories: HarmarLiftOption[];
5698
+ optionalAccessories: HarmarLiftOption[];
5699
+ }
5700
+
5701
+ interface HarmarCompatibleLiftsResult {
5702
+ year: string;
5703
+ vehicleId: string;
5704
+ chairId: string;
5705
+ lifts: HarmarCompatibleLift[];
5706
+ }
5707
+
5708
+ interface HarmarFindCompatibleLiftsResult extends HarmarCompatibleLiftsResult {
5709
+ vehicleMake: string;
5710
+ vehicleModel: string;
5711
+ chairMake: string;
5712
+ chairModel: string;
5713
+ }
5714
+
5715
+ /**
5716
+ * Harmar's Vehicle Compatibility Calculator (calculator.harmar.com) — given a vehicle and a
5717
+ * wheelchair/scooter, returns the real Harmar vehicle lifts that fit that combination.
5718
+ */
5719
+ interface Unit {
5720
+ /**
5721
+ * Every vehicle (make + calculator's own model id) the compatibility calculator has data for
5722
+ * in a given model year.
5723
+ */
5724
+ searchVehicleModels(arg: { year: string }): Promise<HarmarVehicleModel[]>;
5725
+
5726
+ /** Every wheelchair/scooter model the calculator has data for under a given manufacturer make. */
5727
+ searchChairModels(arg: { make: string }): Promise<HarmarChairModel[]>;
5728
+
5729
+ /**
5730
+ * Runs the calculator's own 'Lift Lookup' against its internal vehicle/chair ids and returns
5731
+ * the compatible Harmar lifts.
5732
+ */
5733
+ getCompatibleLifts(arg: { year: string, vehicleId: string, chairId: string }): Promise<HarmarCompatibleLiftsResult>;
5734
+
5735
+ /**
5736
+ * The whole goal-flow in one call: plain vehicle year/make/model + chair make/model, resolved
5737
+ * to the calculator's own ids and run through the real Lift Lookup.
5738
+ */
5739
+ findCompatibleLifts(arg: { year: string, vehicleMake: string, vehicleModel: string, chairMake: string, chairModel: string }): Promise<HarmarFindCompatibleLiftsResult>;
5740
+ }
5741
+ }
5742
+
5222
5743
  declare namespace BowmarkProvider_hauslabs {
5223
5744
  // ── Haus Labs by Lady Gaga — the unit's own declarations, verbatim ──
5224
5745
  interface HauslabsVariant {
@@ -5249,6 +5770,7 @@ interface HauslabsShadeMatch {
5249
5770
  variant: {
5250
5771
  number: number;
5251
5772
  family: string;
5773
+ id: string;
5252
5774
  sku: string | null;
5253
5775
  price: string;
5254
5776
  available: boolean;
@@ -6181,6 +6703,78 @@ interface hiltonRoomOffer {
6181
6703
  }
6182
6704
  }
6183
6705
 
6706
+ declare namespace BowmarkProvider_hobie {
6707
+ // ── Hobie Cat Company — the unit's own declarations, verbatim ──
6708
+ interface HobieModelSummary {
6709
+ slug: string;
6710
+ name: string;
6711
+ url: string;
6712
+ }
6713
+ interface HobieModelColor {
6714
+ color: string;
6715
+ upc: string;
6716
+ }
6717
+ interface HobieModelColors {
6718
+ slug: string;
6719
+ name: string;
6720
+ defaultColor: string;
6721
+ colors: HobieModelColor[];
6722
+ }
6723
+ interface HobieDealer {
6724
+ storeId: number;
6725
+ name: string;
6726
+ address: string;
6727
+ city: string;
6728
+ state: string;
6729
+ zip: string;
6730
+ phoneNumber: string;
6731
+ latitude: number;
6732
+ longitude: number;
6733
+ distanceMiles: number;
6734
+ carriesExactColor: boolean;
6735
+ carriesModel: boolean;
6736
+ carriesBrand: boolean;
6737
+ stockStatus: string;
6738
+ stockDisclaimer: string;
6739
+ }
6740
+ interface HobieLocalAvailability {
6741
+ slug: string;
6742
+ modelName: string;
6743
+ color: string;
6744
+ upc: string;
6745
+ zip: string;
6746
+ dealers: HobieDealer[];
6747
+ dealersCarryingExactColor: number;
6748
+ dealersCarryingModel: number;
6749
+ }
6750
+
6751
+ /**
6752
+ * Reads Hobie Cat Company's own real-time 'Find it Locally' dealer-inventory widget directly —
6753
+ * which real dealer near a zip has a specific kayak model, IN A SPECIFIC COLOR, in stock right
6754
+ * now. Kayaks are dealer-distribution only; there is no first-party checkout.
6755
+ */
6756
+ interface Unit {
6757
+ /**
6758
+ * Lists every real kayak model Hobie currently sells (slug, display name, its own hobie.com
6759
+ * URL), read straight from the live /kayaks/ index.
6760
+ */
6761
+ listModels(): Promise<HobieModelSummary[]>;
6762
+
6763
+ /**
6764
+ * Reads one model's real buildable colors, each paired with the exact UPC the local-inventory
6765
+ * widget is keyed on, plus the site's own default color.
6766
+ */
6767
+ listModelColors(slug: string): Promise<HobieModelColors>;
6768
+
6769
+ /**
6770
+ * Runs Hobie's own real-time 'Find it Locally' widget for one model + color near a US zip and
6771
+ * returns real nearby dealers with Hobie's own exact-color / model / brand carrying flags.
6772
+ * `color` defaults to the site's own default color when omitted.
6773
+ */
6774
+ checkLocalAvailability(slug: string, color: string | undefined, zip: string): Promise<HobieLocalAvailability>;
6775
+ }
6776
+ }
6777
+
6184
6778
  declare namespace BowmarkProvider_hunter {
6185
6779
  // ── Hunter — the unit's own declarations, verbatim ──
6186
6780
  interface hunterDomainCandidate {
@@ -7397,6 +7991,47 @@ interface InteriorDefineCartHandoff {
7397
7991
  }
7398
7992
  }
7399
7993
 
7994
+ declare namespace BowmarkProvider_islllc {
7995
+ // ── Integral Senior Living — the unit's own declarations, verbatim ──
7996
+ type IsllcCareType = "assisted_living" | "independent_living" | "memory_care" | "respite_care";
7997
+
7998
+ interface IsllcCommunity {
7999
+ id: string;
8000
+ name: string;
8001
+ address: string;
8002
+ city: string;
8003
+ state: string;
8004
+ zip: string;
8005
+ phone: string;
8006
+ careTypes: string;
8007
+ lat: number;
8008
+ lng: number;
8009
+ url: string;
8010
+ }
8011
+
8012
+ interface IsllcSearchCommunitiesResult {
8013
+ location: string;
8014
+ geocodedLat: number;
8015
+ geocodedLng: number;
8016
+ careTypes: IsllcCareType[];
8017
+ radiusMiles: number;
8018
+ communities: IsllcCommunity[];
8019
+ }
8020
+
8021
+ /**
8022
+ * Integral Senior Living's own community locator (islllc.com/communities/) — given a US
8023
+ * city/state or ZIP, plus optional care type(s) and radius, returns the real, nearest-first
8024
+ * set of matching ISL communities.
8025
+ */
8026
+ interface Unit {
8027
+ /**
8028
+ * Runs the site's own community locator: a US location + optional care type(s) and radius,
8029
+ * returns the real, nearest-first matching ISL communities.
8030
+ */
8031
+ searchCommunities(arg: { location: string, careTypes?: IsllcCareType[], radiusMiles?: number, maxResults?: number }): Promise<IsllcSearchCommunitiesResult>;
8032
+ }
8033
+ }
8034
+
7400
8035
  declare namespace BowmarkProvider_joybird {
7401
8036
  // ── Joybird — the unit's own declarations, verbatim ──
7402
8037
  interface JoybirdConfigurator {
@@ -7633,6 +8268,106 @@ interface KayakCar {
7633
8268
  }
7634
8269
  }
7635
8270
 
8271
+ declare namespace BowmarkProvider_kitchentuneup {
8272
+ // ── Kitchen Tune-Up — the unit's own declarations, verbatim ──
8273
+ interface KitchentuneupCabinetStyle {
8274
+ featureDefinitionId: number;
8275
+ name: string;
8276
+ group: string | null;
8277
+ thumbnailUrl: string | null;
8278
+ }
8279
+
8280
+ interface KitchentuneupVisualization {
8281
+ resultImageUrl: string;
8282
+ appliedFeatureIds: number[];
8283
+ }
8284
+
8285
+ /**
8286
+ * Kitchen Tune-Up's own AI Design Tool — the live cabinet door/color/finish catalog, and
8287
+ * photo-in/AI-visualization-out generation, run the way kitchentuneup.com/design-tool/ does.
8288
+ */
8289
+ interface Unit {
8290
+ /**
8291
+ * Reads Kitchen Tune-Up's own AI Design Tool catalog off its visualizer vendor's API — every
8292
+ * cabinet door style / color / finish feature currently enabled for the kitchen visualizer,
8293
+ * with the featureDefinitionId visualizeKitchen needs to apply it. Real catalog data, not a
8294
+ * marketing page scrape.
8295
+ */
8296
+ listCabinetStyles(): Promise<KitchentuneupCabinetStyle[]>;
8297
+
8298
+ /**
8299
+ * Runs a photo through Kitchen Tune-Up's own AI Design Tool the way
8300
+ * kitchentuneup.com/design-tool/ does — uploads the photo plus one or more chosen cabinet
8301
+ * features (from listCabinetStyles) to their visualizer vendor's AI image pipeline and returns
8302
+ * the generated visualization image URL. This is the exact functional gap this packet's ANGLE
8303
+ * fit-check recorded: ChatGPT knows the AI Design Tool exists but explicitly refuses to
8304
+ * operate it ('I can't operate Kitchen Tune-Up's website on your behalf') and bounces the user
8305
+ * back to the site.
8306
+ */
8307
+ visualizeKitchen(args: { photoBase64: string, photoFileName?: string, featureDefinitionIds: number[] }): Promise<KitchentuneupVisualization>;
8308
+ }
8309
+ }
8310
+
8311
+ declare namespace BowmarkProvider_kompan {
8312
+ // ── KOMPAN Master — the unit's own declarations, verbatim ──
8313
+ // KOMPAN Master's OWN shapes — not a capability contract.
8314
+
8315
+ type KompanRegion = "region_america" | "region_europe_middleeast" | "region_asia_newzealand" | "region_australia";
8316
+
8317
+ interface KompanVariant {
8318
+ id: string; // pass to getSparePartsDocuments()
8319
+ title: string; // e.g. "PCM157-0205 | UNIVERSAL CAROUSEL"
8320
+ }
8321
+
8322
+ interface KompanSearchResult {
8323
+ productNo: string;
8324
+ region: KompanRegion;
8325
+ found: boolean; // false is a real "no such product number in this region" answer
8326
+ image: string | null;
8327
+ variants: KompanVariant[];
8328
+ }
8329
+
8330
+ interface KompanDocument {
8331
+ section: string; // the site's own heading, e.g. "Layout Drawing", "Installation Instruction"
8332
+ label: string;
8333
+ url: string;
8334
+ }
8335
+
8336
+ interface KompanSparePartsDocuments {
8337
+ variantId: string;
8338
+ purchaseDate: string;
8339
+ title: string | null;
8340
+ documents: KompanDocument[];
8341
+ fullPackageUrl: string | null; // generated on fetch by the site — the URL to fetch, not a static file
8342
+ }
8343
+
8344
+ /**
8345
+ * KOMPAN's own spare-parts / TÜV-certificate / maintenance-manual lookup (KOMPAN Master) —
8346
+ * search a KOMPAN playground product number for its real installed variants, then read the
8347
+ * exact layout drawing, installation instruction, general instruction, on-demand full-package
8348
+ * PDF and language-specific inspection checklists / maintenance manuals for one variant +
8349
+ * purchase date, off the site's own live tool rather than a researched guess.
8350
+ */
8351
+ interface Unit {
8352
+ /**
8353
+ * Searches KOMPAN Master for a product number (e.g. "PCM157") in one region (default
8354
+ * "region_america") and lists every real installed variant of it — each with the internal item
8355
+ * id getSparePartsDocuments() takes. `found: false` is a real, expected answer for a product
8356
+ * number with no record in that region, not an error.
8357
+ */
8358
+ searchProduct(productNo: string, region?: KompanRegion): Promise<KompanSearchResult>;
8359
+
8360
+ /**
8361
+ * Reads the real spare-parts / TÜV-certificate / maintenance-manual documents KOMPAN Master
8362
+ * publishes for one variant (an id from searchProduct()) at one purchase date ("YYYY-MM-DD",
8363
+ * since the site keys the applicable document revision off it) — layout drawing, installation
8364
+ * instruction, general instruction, additional checklists/manuals, and the on-demand "full
8365
+ * package" PDF URL.
8366
+ */
8367
+ getSparePartsDocuments(variantId: string, purchaseDate: string): Promise<KompanSparePartsDocuments>;
8368
+ }
8369
+ }
8370
+
7636
8371
  declare namespace BowmarkProvider_labcorp {
7637
8372
  // ── Labcorp — the unit's own declarations, verbatim ──
7638
8373
  interface LabcorpTestSummary {
@@ -8059,6 +8794,61 @@ interface LonelyPlanetSearchResult {
8059
8794
  }
8060
8795
  }
8061
8796
 
8797
+ declare namespace BowmarkProvider_louvershop {
8798
+ // ── Louver Shop Shutters — the unit's own declarations, verbatim ──
8799
+ interface LouvershopBranch {
8800
+ id: number;
8801
+ name: string;
8802
+ link: string;
8803
+ tel: string;
8804
+ }
8805
+
8806
+ interface LouvershopLocalArea {
8807
+ zip: string;
8808
+ city: string;
8809
+ state: string;
8810
+ stateCode: string;
8811
+ }
8812
+
8813
+ interface LouvershopConsultant {
8814
+ id: number;
8815
+ name: string;
8816
+ link: string;
8817
+ }
8818
+
8819
+ interface LouvershopAvailability {
8820
+ exteriorDecorative: boolean;
8821
+ exteriorSecurity: boolean;
8822
+ }
8823
+
8824
+ interface FindLocalDealerResult {
8825
+ zip: string;
8826
+ inServiceArea: boolean;
8827
+ branch: LouvershopBranch | null;
8828
+ area: LouvershopLocalArea | null;
8829
+ consultants: LouvershopConsultant[];
8830
+ availability: LouvershopAvailability | null;
8831
+ }
8832
+
8833
+ /**
8834
+ * Window treatments (interior/exterior shutters, blinds, shades) dealer network.
8835
+ * findLocalDealer is live — the same lookup the site's own free-quote/consultant-locator forms
8836
+ * run before showing a booking path, given a ZIP. requestConsultation (submitting the actual
8837
+ * in-home consultation request) is a stub — see its notImplemented reason.
8838
+ */
8839
+ interface Unit {
8840
+ /**
8841
+ * Looks up the Louver Shop dealer/branch that covers a US ZIP (`zip`, a 4-5 digit string, e.g.
8842
+ * "30301") — the same lookup the site's own "Free In-Home Design Consultation" form and "Find
8843
+ * a Consultant" locator both run before offering a booking path. Returns whether the ZIP is in
8844
+ * the dealer network, the matched branch (name, phone, page slug), the normalized local area,
8845
+ * the branch's assigned consultants (deduplicated), and per-branch exterior-shutter
8846
+ * availability flags. Recovered from the locator widget's own backend, not guessed at.
8847
+ */
8848
+ findLocalDealer(args: object): Promise<FindLocalDealerResult>;
8849
+ }
8850
+ }
8851
+
8062
8852
  declare namespace BowmarkProvider_lufthansa {
8063
8853
  // ── Lufthansa — the unit's own declarations, verbatim ──
8064
8854
  interface LufthansaFlightLeg {
@@ -8316,9 +9106,54 @@ interface LululemonProductAttributes {
8316
9106
  * reviews on products whose live page shows 22,748. */
8317
9107
  ratingValue: number | null;
8318
9108
  reviewCount: number | null;
9109
+ /** Per-FIELD origin for the eight facts above, keyed by the same names. THIS
9110
+ * is how a refused read is told apart from a garment with nothing published:
9111
+ * fabrics [] beside status "absent" is lululemon saying it names no fabric,
9112
+ * and fabrics [] beside status "unreachable" is the page refusing us. Ranking
9113
+ * that treats the two alike silently prefers whichever candidates loaded. */
9114
+ provenance: Record<string, FieldProvenance>;
9115
+ /** The roll-up. On a refused page ratio is 0 and unreachableFields names all
9116
+ * eight — the machine-readable form of the sentence in warnings. */
9117
+ completeness: Completeness;
8319
9118
  /** What could not be reached. Non-empty means the page refused. */
8320
9119
  warnings: string[];
8321
9120
  }
9121
+ /** Where one field's value came from, and the retailer text behind it. */
9122
+ interface FieldProvenance {
9123
+ /** "published" — lululemon stated it. "absent" — the page rendered and said
9124
+ * nothing about this field. "unreachable" — the page refused, so UNKNOWN. */
9125
+ status: "published" | "absent" | "unreachable";
9126
+ /** Which door: "lululemon_pdp_ldjson", "lululemon_pdp_accordion" or
9127
+ * "lululemon_pdp_title". Null when nothing filled it. */
9128
+ source: string | null;
9129
+ /** The retailer's own words the value rests on, for a field derived from
9130
+ * prose. Null for a field the site published as a typed value. */
9131
+ evidence: string | null;
9132
+ /** On "unreachable" only: what was refused. */
9133
+ detail?: string;
9134
+ }
9135
+ interface Completeness {
9136
+ fields: number;
9137
+ published: number;
9138
+ absent: number;
9139
+ unreachable: number;
9140
+ /** published / fields, to 3dp. */
9141
+ ratio: number;
9142
+ /** The fields that are UNKNOWN rather than known-empty. */
9143
+ unreachableFields: string[];
9144
+ sourcesUsed: string[];
9145
+ }
9146
+ /** What getProducts returns. PARTIAL by construction — the pricing catalogue
9147
+ * holds ~39% of the ids in lululemon's own sitemap, so ids it does not carry are
9148
+ * NAMED rather than silently dropped or thrown over. */
9149
+ interface LululemonProductBatch {
9150
+ /** In the order the ids were passed, not the order they finished. */
9151
+ products: LululemonProduct[];
9152
+ /** Every id that did not read, with the catalogue's own sentence. */
9153
+ missing: Array<{ productId: string; detail: string }>;
9154
+ requested: number;
9155
+ warnings: string[];
9156
+ }
8322
9157
  interface LululemonRow {
8323
9158
  id: string;
8324
9159
  title: string;
@@ -8373,6 +9208,17 @@ interface LululemonSimilarProducts {
8373
9208
  */
8374
9209
  getProduct(query: { productId: string }): Promise<LululemonProduct>;
8375
9210
 
9211
+ /**
9212
+ * Reads the full configurator for MANY products in one call — the shape to use when ranking a
9213
+ * candidate set, because a `search` row carries a price range and a colour count but not the
9214
+ * per-colourway sizes, markdown evidence or images a ranking turns on. Returns `products` in
9215
+ * the order the ids were passed. PARTIAL is the normal answer: the pricing catalogue holds
9216
+ * roughly 39% of the ids in lululemon's own sitemap, so ids it does not carry come back in
9217
+ * `missing` with the catalogue's own sentence, and one of them never costs the other rows. At
9218
+ * most 24 ids — the same cap `search` returns — so one full search page is always one batch.
9219
+ */
9220
+ getProducts(query: { productIds: string[] }): Promise<LululemonProductBatch>;
9221
+
8376
9222
  /**
8377
9223
  * Reads what lululemon's OWN product page publishes about a garment and the third-party
8378
9224
  * pricing door does not carry at all: the category the site files it under, the collection
@@ -10792,6 +11638,10 @@ interface PremierbuildingsStyle {
10792
11638
  label: string;
10793
11639
  sidingOptions: string[];
10794
11640
  sizes: { sizeKey: string; width: number; length: number }[];
11641
+ imageUrl: string | null; // a real product image from Premier's own catalogue
11642
+ roofStyle: string | null; // "gable", "gambrel", ...
11643
+ roofing: string | null; // "metal", ...
11644
+ wallHeight: string | null; // Premier's own encoding in inches, e.g. "left-78-right-78-eave-72"
10795
11645
  }
10796
11646
  interface PremierbuildingsPrice {
10797
11647
  styleKey: string;
@@ -10825,8 +11675,10 @@ interface PremierbuildingsDealer {
10825
11675
  interface Unit {
10826
11676
  /**
10827
11677
  * Lists every real building style Premier's ShedView configurator offers (Lofted Barn,
10828
- * Utility, Cabin, Garage, ...) with its real siding options and every real buildable size
10829
- * (width x length), read straight from the configurator's own live catalogue.
11678
+ * Utility, Cabin, Garage, ...) with its real siding options, every real buildable size (width
11679
+ * x length), and what the building actually IS — a real product image, the roof line and
11680
+ * roofing material, and Premier's own wall-height spec — read straight from the configurator's
11681
+ * own live catalogue.
10830
11682
  */
10831
11683
  listBuildingStyles(): Promise<PremierbuildingsStyle[]>;
10832
11684
 
@@ -14384,6 +15236,57 @@ interface wellfoundCompanyDetail {
14384
15236
  }
14385
15237
  }
14386
15238
 
15239
+ declare namespace BowmarkProvider_yourarborhome {
15240
+ // ── Arbor Homes — the unit's own declarations, verbatim ──
15241
+ interface ArborHome {
15242
+ uniqueName: string;
15243
+ headline: string;
15244
+ status: string;
15245
+ price: number | null;
15246
+ beds: number | null;
15247
+ bathsFull: number | null;
15248
+ bathsHalf: number | null;
15249
+ sqft: number | null;
15250
+ stories: number | null;
15251
+ moveInDate: string | null;
15252
+ address: { street: string; city: string; state: string; postalCode: string };
15253
+ detailUrl: string | null;
15254
+ selfTourUrl: string | null;
15255
+ }
15256
+ interface SearchHomesFilters {
15257
+ city?: string;
15258
+ minPrice?: number;
15259
+ maxPrice?: number;
15260
+ minBeds?: number;
15261
+ minBaths?: number;
15262
+ minSqft?: number;
15263
+ status?: string;
15264
+ }
15265
+
15266
+ /**
15267
+ * Arbor Homes — Indiana/Ohio/Kentucky new-construction homebuilder (Clayton Properties Group).
15268
+ * searchHomes reads its live quick-move-in inventory (price, beds/baths/sqft, status,
15269
+ * availability); getHome reads one listing by id. Both return the site's own NterNow
15270
+ * self-guided-tour booking link where the listing has it enabled.
15271
+ */
15272
+ interface Unit {
15273
+ /**
15274
+ * Reads Arbor Homes' live quick move-in inventory off yourarborhome.com/homes and returns rows
15275
+ * matching the optional filters (city, price range, min beds/baths/sqft, status) — real price,
15276
+ * beds/baths, square footage, an availability date and, where the listing has it enabled,
15277
+ * Arbor's own NterNow self-guided-tour booking link.
15278
+ */
15279
+ searchHomes(filters?: SearchHomesFilters): Promise<ArborHome[]>;
15280
+
15281
+ /**
15282
+ * Reads one Arbor Homes listing by the `uniqueName` id `searchHomes` returns — the same row,
15283
+ * for a caller that already picked a home and wants its detail-page URL and self-tour link
15284
+ * without re-filtering the whole search.
15285
+ */
15286
+ getHome(uniqueName: string): Promise<ArborHome>;
15287
+ }
15288
+ }
15289
+
14387
15290
  declare namespace BowmarkFamily_shopify_store {
14388
15291
  // ── Shopify storefronts — the unit's own declarations, verbatim ──
14389
15292
  interface ShopifyVariant {
@@ -14431,8 +15334,8 @@ interface SaleEvidence {
14431
15334
  /** The published "was" price. Strictly greater than currentPrice when
14432
15335
  * evidenceType is "compare_at_price". Null when nothing published one. */
14433
15336
  originalPrice: string | null;
14434
- /** ISO 4217, or null. Null on the REST catalogue door, which publishes no
14435
- * currencyreported honestly rather than assumed to be USD. */
15337
+ /** ISO 4217, from the storefront's own /meta.json. Null only when that read
15338
+ * failednever defaulted to "USD", which is wrong on every non-US store. */
14436
15339
  currency: string | null;
14437
15340
  promotionMessage: string | null;
14438
15341
  /** How it was decided. "compare_at_price" is a struck-through price;
@@ -14461,6 +15364,11 @@ interface PublishedProductAttributes {
14461
15364
  sleeveLength: string | null;
14462
15365
  rise: string | null;
14463
15366
  waistband: string | null;
15367
+ /** How long the GARMENT is — "Cropped", "Midi". What every store's own
15368
+ * `length::` tag fills, on a top exactly as on a bottom. */
15369
+ garmentLength: string | null;
15370
+ /** The inside leg seam and only that. NULL on a top — a tank has no inseam,
15371
+ * and its length is `garmentLength`. */
14464
15372
  inseam: string | null;
14465
15373
  legShape: string | null;
14466
15374
  fit: string | null;
@@ -14479,8 +15387,14 @@ interface CoordinationMetadata {
14479
15387
  productFamily: string | null;
14480
15388
  }
14481
15389
  /** An EXPLICIT retailer-published relationship. Shared colour, fabric,
14482
- * collection or family is NOT this — that is CoordinationMetadata. Usually [];
14483
- * the catalogue door carries no set tags on any store measured. */
15390
+ * collection or family is NOT this — that is CoordinationMetadata. Usually [].
15391
+ *
15392
+ * Two doors fill it. On a PRODUCT row it comes from the store's tags, and no
15393
+ * store measured publishes a set tag, so it is [] there. getSetEvidence() reads
15394
+ * the other one: the complementary products a MERCHANDISER pinned by hand,
15395
+ * admitted only for rows the store marks pr_prod_strat=pinned. The algorithmic
15396
+ * "related products" feed is never read into this — that is a recommendation
15397
+ * engine's output, not the retailer stating a pairing. */
14484
15398
  interface RetailerSetEvidence {
14485
15399
  evidenceType: "official_set" | "shop_the_set" | "complete_the_look" | "matching_piece";
14486
15400
  evidenceText: string | null;
@@ -14488,6 +15402,48 @@ interface RetailerSetEvidence {
14488
15402
  setId: string | null;
14489
15403
  relatedProducts: Array<{ productId: string | null; handle: string | null; title: string | null; url: string | null }>;
14490
15404
  }
15405
+ /** What getSetEvidence returns. An OBJECT rather than a bare array so the
15406
+ * healthy EMPTY answer is still probeable: productId and sourceUrl exist only
15407
+ * when BOTH hops answered, where an empty evidence list is the ordinary case. */
15408
+ interface ShopifySetEvidence {
15409
+ handle: string;
15410
+ /** Shopify's numeric PRODUCT id — the only key the recommendations door takes,
15411
+ * and published by no other function here. */
15412
+ productId: string;
15413
+ /** The product page the pairing is published on. */
15414
+ sourceUrl: string;
15415
+ /** ONLY the rows a merchandiser pinned by hand. [] when they pinned nothing,
15416
+ * which is the common case and is the STORE's answer. At most one entry — one
15417
+ * statement, N related products. Read this for "what did the retailer SAY". */
15418
+ evidence: RetailerSetEvidence[];
15419
+ /** EVERY row the store recommends, pinned AND algorithmic, deduplicated, each
15420
+ * labelled. Read source.by before treating one as the retailer's decision —
15421
+ * the algorithmic rows are usually SUBSTITUTES rather than companions, since
15422
+ * similarity returns the nearest product and the nearest thing to a baby
15423
+ * monitor is another baby monitor. [] when includeAlgorithmic was false. */
15424
+ recommendations: RecommendedProduct[];
15425
+ /** Non-empty when the store returned a FULL page for an intent, which cannot be
15426
+ * told apart from a longer list cut off at the cap. A merchandiser's pairing is
15427
+ * a statement, so a partial one must not read as the whole. */
15428
+ warnings: string[];
15429
+ }
15430
+ /** One recommended product, with the store's own label for who chose it. */
15431
+ interface RecommendedProduct {
15432
+ productId: string | null;
15433
+ handle: string | null;
15434
+ title: string | null;
15435
+ url: string | null;
15436
+ source: RecommendationSource;
15437
+ }
15438
+ interface RecommendationSource {
15439
+ /** "retailer" when a merchandiser pinned it; "algorithm" otherwise, INCLUDING
15440
+ * a row the store labelled with nothing — an unknown provenance must never
15441
+ * read as a person's decision. */
15442
+ by: "retailer" | "algorithm";
15443
+ /** The store's own token, verbatim and unmapped — "pinned", "jac" (Jaccard),
15444
+ * "e" (embedding), "collection_fallback". Null when the row carried none. */
15445
+ strategy: string | null;
15446
+ }
14491
15447
  interface ShopifyProduct {
14492
15448
  handle: string;
14493
15449
  title: string;
@@ -14502,13 +15458,64 @@ interface ShopifyProduct {
14502
15458
  descriptionHtml: string | null;
14503
15459
  /** The same copy with its markup removed. */
14504
15460
  descriptionText: string | null;
15461
+ /** ISO 4217, read once per store from its own /meta.json. */
14505
15462
  currency: string | null;
15463
+ /** When the store's door ANSWERED this row, ISO 8601 UTC. Every field here is
15464
+ * a live fact with a shelf life — the price, the markdown, the per-variant
15465
+ * stock flag — so read it before treating a cached row as current. Stamped per
15466
+ * REQUEST: a getProducts batch carries one stamp per handle, not one per call. */
15467
+ fetchedAt: string;
14506
15468
  /** Every image the door published, deduplicated, in the store's own order. */
14507
15469
  images: ProductImage[];
14508
15470
  attributes: PublishedProductAttributes;
15471
+ /** Per-FIELD origin for `attributes` — keyed by the same field names. Read it
15472
+ * before ranking on a null: "absent" is the store publishing nothing, and is
15473
+ * the store's own answer; "unreachable" is a door we could not read, and means
15474
+ * UNKNOWN. On this store both doors are one request that either answered or
15475
+ * threw, so nothing here is ever "unreachable" — the status exists because the
15476
+ * same vocabulary is used by providers whose page can refuse mid-answer. */
15477
+ attributeProvenance: Record<string, FieldProvenance>;
15478
+ attributeCompleteness: Completeness;
14509
15479
  coordination: CoordinationMetadata;
14510
15480
  retailerSetEvidence: RetailerSetEvidence[];
14511
15481
  }
15482
+ /** Where one attribute value came from, and the retailer text behind it. */
15483
+ interface FieldProvenance {
15484
+ /** "published" — the retailer stated it. "absent" — every door was silent.
15485
+ * "unreachable" — a door that would carry it was refused, so it is UNKNOWN. */
15486
+ status: "published" | "absent" | "unreachable";
15487
+ /** Which door: "shopify_tags", "shopify_product_type", "shopify_color_option"
15488
+ * or "shopify_product_copy". Null when nothing filled it. */
15489
+ source: string | null;
15490
+ /** The retailer's own words the value rests on, verbatim — the tag, or the
15491
+ * sentence out of the description. Null when nothing filled it. */
15492
+ evidence: string | null;
15493
+ /** On "unreachable" only: what was refused. */
15494
+ detail?: string;
15495
+ }
15496
+ /** The roll-up over one product's attributeProvenance. */
15497
+ interface Completeness {
15498
+ fields: number;
15499
+ published: number;
15500
+ absent: number;
15501
+ unreachable: number;
15502
+ /** published / fields, to 3dp. */
15503
+ ratio: number;
15504
+ /** The fields whose value is UNKNOWN rather than known-empty. Read this before
15505
+ * comparing two rows: a row with entries here was not fully looked at. */
15506
+ unreachableFields: string[];
15507
+ sourcesUsed: string[];
15508
+ }
15509
+ /** What getProducts returns. PARTIAL by design: one handle the store will not
15510
+ * serve costs that row and nothing else, where getProduct throws. */
15511
+ interface ShopifyProductBatch {
15512
+ /** In the order the handles were passed, not the order they finished. */
15513
+ products: ShopifyProduct[];
15514
+ /** Every handle the store did not serve, with what it said. */
15515
+ missing: Array<{ handle: string; detail: string }>;
15516
+ requested: number;
15517
+ warnings: string[];
15518
+ }
14512
15519
  interface ShopifyCartLine {
14513
15520
  /** Shopify's own line key, which its cart-change endpoints address a line by. */
14514
15521
  lineKey: string;
@@ -14544,6 +15551,46 @@ interface ShopifyCollectionProducts {
14544
15551
  * top-then-bottom; reading a pairing out of that order is the caller's
14545
15552
  * inference, never this provider's claim. [] is an ordinary answer. */
14546
15553
  products: ShopifyProduct[];
15554
+ /** Pass back as opts.cursor for the next page, or NULL when this is the last
15555
+ * one. A collection bigger than one page is reachable only through this. */
15556
+ cursor: string | null;
15557
+ /** Empty on an ordinary page. One entry when the walk hit the store platform's
15558
+ * 25,000-row ceiling with the collection unfinished — a TRUNCATION, which a
15559
+ * null cursor on its own would read as the end of the list. */
15560
+ warnings: string[];
15561
+ }
15562
+ /** One page of a whole-catalogue walk. Advance it with the cursor; there is
15563
+ * deliberately no "fetch everything" call, because every row is a request
15564
+ * against the store and only the caller knows how many candidates it needs. */
15565
+ interface ShopifyProductPage {
15566
+ /** In the store's own MERCHANDISED order — not id, not date. The store may
15567
+ * re-merchandise mid-walk, so key on handle rather than assuming pages are
15568
+ * disjoint. */
15569
+ products: ShopifyProduct[];
15570
+ /** Pass back as opts.cursor for the next page. NULL when the store answered a
15571
+ * short page, which is what the end of the catalogue looks like. */
15572
+ cursor: string | null;
15573
+ /** How many rows this page asked the store for. */
15574
+ limit: number;
15575
+ /** Empty on an ordinary page. One entry when the walk stopped at the 25,000-row
15576
+ * ceiling with the catalogue unfinished. */
15577
+ warnings: string[];
15578
+ }
15579
+ /** What resolveProductUrl hands back — the product a url names, and the variant
15580
+ * its own ?variant= selected. */
15581
+ interface ShopifyProductFromUrl {
15582
+ product: ShopifyProduct;
15583
+ /** The variant ?variant= named, or NULL when the url named none — the ordinary
15584
+ * case for a link off a collection page. Also null when it named one the store
15585
+ * no longer publishes, which warnings says. NEVER the first variant instead:
15586
+ * that answers "is my size in stock" about a different size. */
15587
+ variant: ShopifyVariant | null;
15588
+ /** The ?variant= value exactly as the url carried it, kept even when it
15589
+ * matched nothing — a stale link is a fact about the link. */
15590
+ variantIdInUrl: string | null;
15591
+ /** Empty on a clean resolve. One entry when the url named a variant the store
15592
+ * no longer publishes. */
15593
+ warnings: string[];
14547
15594
  }
14548
15595
  interface ShopifyCart {
14549
15596
  /** The store's own cart token — a bearer credential, so treat it like one. */
@@ -14572,10 +15619,45 @@ interface ShopifyCart {
14572
15619
 
14573
15620
  /**
14574
15621
  * Reads one product by handle — every variant, its exact price, its SKU and whether that
14575
- * specific size or colour is purchasable right now.
15622
+ * specific size or colour is purchasable right now. Pass { withReviews: true } to ALSO get the
15623
+ * star rating and review count from whichever review app the merchant installed; it is off by
15624
+ * default because it costs a second origin and usually the rendered product page too.
14576
15625
  */
14577
15626
  getProduct(handle: string): Promise<ShopifyProduct>;
14578
15627
 
15628
+ /**
15629
+ * Turns a product URL into the product, which is the address a caller actually holds when a
15630
+ * link arrives from a search result, a page or a person. Takes the whole url — origin, market
15631
+ * prefix, ?variant= and #fragment — so nothing has to be stripped down to a bare handle first,
15632
+ * and the variant the url named comes back beside the product instead of being lost. THROWS,
15633
+ * naming the domain, on a url belonging to another storefront. Takes the same { withReviews:
15634
+ * true } option as getProduct, on the same default: a url is another way of naming one
15635
+ * product, so holding a link rather than a handle must not cost a caller the rating.
15636
+ */
15637
+ resolveProductUrl(url: string): Promise<ShopifyProductFromUrl>;
15638
+
15639
+ /**
15640
+ * Reads FULL detail for many products in one call — the shape for ranking a candidate set,
15641
+ * since a search row carries neither the description copy nor the per-variant stock a ranking
15642
+ * turns on. PARTIAL by construction: a search row's handle may 404 on the Ajax product door
15643
+ * (measured 2026-08-05, 2 of 10 sampled members), so one bad handle is named in `missing` and
15644
+ * costs that row alone, where `getProduct` throws and takes the whole set with it. Capped at
15645
+ * 50 handles because each one is a request to the store. Takes the same { withReviews: true }
15646
+ * option, and is the shape to use for it: the review app's key is learned from the first pages
15647
+ * and reused, so ratings for twenty handles cost a handful of page fetches rather than twenty.
15648
+ */
15649
+ getProducts(handles: string[]): Promise<ShopifyProductBatch>;
15650
+
15651
+ /**
15652
+ * Walks the store's WHOLE catalogue a page at a time, in its own merchandised order — the
15653
+ * shape that makes a realistic candidate set reachable at all, since search ranks against a
15654
+ * query and returns one slice. The caller advances a cursor and stops on its own budget or on
15655
+ * a null cursor. There is deliberately no fetch-everything call: every row is a request
15656
+ * against a stranger's storefront, and how many candidates a ranking needs is the caller's
15657
+ * decision rather than one taken once, inside the library, on behalf of every member.
15658
+ */
15659
+ listProducts(opts?: { limit?: number; cursor?: string | null }): Promise<ShopifyProductPage>;
15660
+
14579
15661
  /**
14580
15662
  * Lists the store's own merchandised collections. THIS is where a retailer states a SET: a
14581
15663
  * collection the store itself named "Matching Sets" or "Activewear Sets" carries setLike:
@@ -14588,7 +15670,18 @@ interface ShopifyCart {
14588
15670
  * rows. An empty list is an ordinary answer — several named 'look' collections publish no
14589
15671
  * products through this door.
14590
15672
  */
14591
- getCollection(handle: string, opts?: { limit?: number }): Promise<ShopifyCollectionProducts>;
15673
+ getCollection(handle: string, opts?: { limit?: number; cursor?: string | null }): Promise<ShopifyCollectionProducts>;
15674
+
15675
+ /**
15676
+ * Two answers in one call. `evidence` is ONLY what a MERCHANDISER pinned by hand, in the order
15677
+ * they typed it — an explicit retailer statement, and [] on most products, which is the
15678
+ * retailer's own answer. `recommendations` is EVERY row the store returns including the
15679
+ * algorithmic ones, each carrying source.by (retailer | algorithm) and the store's own
15680
+ * strategy token, so a caller can use them without either field lying. The algorithmic rows
15681
+ * are usually SUBSTITUTES rather than companions. Pass includeAlgorithmic: false to skip the
15682
+ * second request.
15683
+ */
15684
+ getSetEvidence(handle: string, opts?: { includeAlgorithmic?: boolean }): Promise<ShopifySetEvidence>;
14592
15685
 
14593
15686
  /**
14594
15687
  * Puts variants into THIS run's own cart on the store and returns the cart the store reports
@@ -14654,8 +15747,8 @@ interface SaleEvidence {
14654
15747
  /** The published "was" price. Strictly greater than currentPrice when
14655
15748
  * evidenceType is "compare_at_price". Null when nothing published one. */
14656
15749
  originalPrice: string | null;
14657
- /** ISO 4217, or null. Null on the REST catalogue door, which publishes no
14658
- * currencyreported honestly rather than assumed to be USD. */
15750
+ /** ISO 4217, from the storefront's own /meta.json. Null only when that read
15751
+ * failednever defaulted to "USD", which is wrong on every non-US store. */
14659
15752
  currency: string | null;
14660
15753
  promotionMessage: string | null;
14661
15754
  /** How it was decided. "compare_at_price" is a struck-through price;
@@ -14684,6 +15777,11 @@ interface PublishedProductAttributes {
14684
15777
  sleeveLength: string | null;
14685
15778
  rise: string | null;
14686
15779
  waistband: string | null;
15780
+ /** How long the GARMENT is — "Cropped", "Midi". What every store's own
15781
+ * `length::` tag fills, on a top exactly as on a bottom. */
15782
+ garmentLength: string | null;
15783
+ /** The inside leg seam and only that. NULL on a top — a tank has no inseam,
15784
+ * and its length is `garmentLength`. */
14687
15785
  inseam: string | null;
14688
15786
  legShape: string | null;
14689
15787
  fit: string | null;
@@ -14702,8 +15800,14 @@ interface CoordinationMetadata {
14702
15800
  productFamily: string | null;
14703
15801
  }
14704
15802
  /** An EXPLICIT retailer-published relationship. Shared colour, fabric,
14705
- * collection or family is NOT this — that is CoordinationMetadata. Usually [];
14706
- * the catalogue door carries no set tags on any store measured. */
15803
+ * collection or family is NOT this — that is CoordinationMetadata. Usually [].
15804
+ *
15805
+ * Two doors fill it. On a PRODUCT row it comes from the store's tags, and no
15806
+ * store measured publishes a set tag, so it is [] there. getSetEvidence() reads
15807
+ * the other one: the complementary products a MERCHANDISER pinned by hand,
15808
+ * admitted only for rows the store marks pr_prod_strat=pinned. The algorithmic
15809
+ * "related products" feed is never read into this — that is a recommendation
15810
+ * engine's output, not the retailer stating a pairing. */
14707
15811
  interface RetailerSetEvidence {
14708
15812
  evidenceType: "official_set" | "shop_the_set" | "complete_the_look" | "matching_piece";
14709
15813
  evidenceText: string | null;
@@ -14711,6 +15815,48 @@ interface RetailerSetEvidence {
14711
15815
  setId: string | null;
14712
15816
  relatedProducts: Array<{ productId: string | null; handle: string | null; title: string | null; url: string | null }>;
14713
15817
  }
15818
+ /** What getSetEvidence returns. An OBJECT rather than a bare array so the
15819
+ * healthy EMPTY answer is still probeable: productId and sourceUrl exist only
15820
+ * when BOTH hops answered, where an empty evidence list is the ordinary case. */
15821
+ interface ShopifySetEvidence {
15822
+ handle: string;
15823
+ /** Shopify's numeric PRODUCT id — the only key the recommendations door takes,
15824
+ * and published by no other function here. */
15825
+ productId: string;
15826
+ /** The product page the pairing is published on. */
15827
+ sourceUrl: string;
15828
+ /** ONLY the rows a merchandiser pinned by hand. [] when they pinned nothing,
15829
+ * which is the common case and is the STORE's answer. At most one entry — one
15830
+ * statement, N related products. Read this for "what did the retailer SAY". */
15831
+ evidence: RetailerSetEvidence[];
15832
+ /** EVERY row the store recommends, pinned AND algorithmic, deduplicated, each
15833
+ * labelled. Read source.by before treating one as the retailer's decision —
15834
+ * the algorithmic rows are usually SUBSTITUTES rather than companions, since
15835
+ * similarity returns the nearest product and the nearest thing to a baby
15836
+ * monitor is another baby monitor. [] when includeAlgorithmic was false. */
15837
+ recommendations: RecommendedProduct[];
15838
+ /** Non-empty when the store returned a FULL page for an intent, which cannot be
15839
+ * told apart from a longer list cut off at the cap. A merchandiser's pairing is
15840
+ * a statement, so a partial one must not read as the whole. */
15841
+ warnings: string[];
15842
+ }
15843
+ /** One recommended product, with the store's own label for who chose it. */
15844
+ interface RecommendedProduct {
15845
+ productId: string | null;
15846
+ handle: string | null;
15847
+ title: string | null;
15848
+ url: string | null;
15849
+ source: RecommendationSource;
15850
+ }
15851
+ interface RecommendationSource {
15852
+ /** "retailer" when a merchandiser pinned it; "algorithm" otherwise, INCLUDING
15853
+ * a row the store labelled with nothing — an unknown provenance must never
15854
+ * read as a person's decision. */
15855
+ by: "retailer" | "algorithm";
15856
+ /** The store's own token, verbatim and unmapped — "pinned", "jac" (Jaccard),
15857
+ * "e" (embedding), "collection_fallback". Null when the row carried none. */
15858
+ strategy: string | null;
15859
+ }
14714
15860
  interface ShopifyProduct {
14715
15861
  handle: string;
14716
15862
  title: string;
@@ -14725,13 +15871,64 @@ interface ShopifyProduct {
14725
15871
  descriptionHtml: string | null;
14726
15872
  /** The same copy with its markup removed. */
14727
15873
  descriptionText: string | null;
15874
+ /** ISO 4217, read once per store from its own /meta.json. */
14728
15875
  currency: string | null;
15876
+ /** When the store's door ANSWERED this row, ISO 8601 UTC. Every field here is
15877
+ * a live fact with a shelf life — the price, the markdown, the per-variant
15878
+ * stock flag — so read it before treating a cached row as current. Stamped per
15879
+ * REQUEST: a getProducts batch carries one stamp per handle, not one per call. */
15880
+ fetchedAt: string;
14729
15881
  /** Every image the door published, deduplicated, in the store's own order. */
14730
15882
  images: ProductImage[];
14731
15883
  attributes: PublishedProductAttributes;
15884
+ /** Per-FIELD origin for `attributes` — keyed by the same field names. Read it
15885
+ * before ranking on a null: "absent" is the store publishing nothing, and is
15886
+ * the store's own answer; "unreachable" is a door we could not read, and means
15887
+ * UNKNOWN. On this store both doors are one request that either answered or
15888
+ * threw, so nothing here is ever "unreachable" — the status exists because the
15889
+ * same vocabulary is used by providers whose page can refuse mid-answer. */
15890
+ attributeProvenance: Record<string, FieldProvenance>;
15891
+ attributeCompleteness: Completeness;
14732
15892
  coordination: CoordinationMetadata;
14733
15893
  retailerSetEvidence: RetailerSetEvidence[];
14734
15894
  }
15895
+ /** Where one attribute value came from, and the retailer text behind it. */
15896
+ interface FieldProvenance {
15897
+ /** "published" — the retailer stated it. "absent" — every door was silent.
15898
+ * "unreachable" — a door that would carry it was refused, so it is UNKNOWN. */
15899
+ status: "published" | "absent" | "unreachable";
15900
+ /** Which door: "shopify_tags", "shopify_product_type", "shopify_color_option"
15901
+ * or "shopify_product_copy". Null when nothing filled it. */
15902
+ source: string | null;
15903
+ /** The retailer's own words the value rests on, verbatim — the tag, or the
15904
+ * sentence out of the description. Null when nothing filled it. */
15905
+ evidence: string | null;
15906
+ /** On "unreachable" only: what was refused. */
15907
+ detail?: string;
15908
+ }
15909
+ /** The roll-up over one product's attributeProvenance. */
15910
+ interface Completeness {
15911
+ fields: number;
15912
+ published: number;
15913
+ absent: number;
15914
+ unreachable: number;
15915
+ /** published / fields, to 3dp. */
15916
+ ratio: number;
15917
+ /** The fields whose value is UNKNOWN rather than known-empty. Read this before
15918
+ * comparing two rows: a row with entries here was not fully looked at. */
15919
+ unreachableFields: string[];
15920
+ sourcesUsed: string[];
15921
+ }
15922
+ /** What getProducts returns. PARTIAL by design: one handle the store will not
15923
+ * serve costs that row and nothing else, where getProduct throws. */
15924
+ interface ShopifyProductBatch {
15925
+ /** In the order the handles were passed, not the order they finished. */
15926
+ products: ShopifyProduct[];
15927
+ /** Every handle the store did not serve, with what it said. */
15928
+ missing: Array<{ handle: string; detail: string }>;
15929
+ requested: number;
15930
+ warnings: string[];
15931
+ }
14735
15932
  interface ShopifyCartLine {
14736
15933
  /** Shopify's own line key, which its cart-change endpoints address a line by. */
14737
15934
  lineKey: string;
@@ -14767,6 +15964,46 @@ interface ShopifyCollectionProducts {
14767
15964
  * top-then-bottom; reading a pairing out of that order is the caller's
14768
15965
  * inference, never this provider's claim. [] is an ordinary answer. */
14769
15966
  products: ShopifyProduct[];
15967
+ /** Pass back as opts.cursor for the next page, or NULL when this is the last
15968
+ * one. A collection bigger than one page is reachable only through this. */
15969
+ cursor: string | null;
15970
+ /** Empty on an ordinary page. One entry when the walk hit the store platform's
15971
+ * 25,000-row ceiling with the collection unfinished — a TRUNCATION, which a
15972
+ * null cursor on its own would read as the end of the list. */
15973
+ warnings: string[];
15974
+ }
15975
+ /** One page of a whole-catalogue walk. Advance it with the cursor; there is
15976
+ * deliberately no "fetch everything" call, because every row is a request
15977
+ * against the store and only the caller knows how many candidates it needs. */
15978
+ interface ShopifyProductPage {
15979
+ /** In the store's own MERCHANDISED order — not id, not date. The store may
15980
+ * re-merchandise mid-walk, so key on handle rather than assuming pages are
15981
+ * disjoint. */
15982
+ products: ShopifyProduct[];
15983
+ /** Pass back as opts.cursor for the next page. NULL when the store answered a
15984
+ * short page, which is what the end of the catalogue looks like. */
15985
+ cursor: string | null;
15986
+ /** How many rows this page asked the store for. */
15987
+ limit: number;
15988
+ /** Empty on an ordinary page. One entry when the walk stopped at the 25,000-row
15989
+ * ceiling with the catalogue unfinished. */
15990
+ warnings: string[];
15991
+ }
15992
+ /** What resolveProductUrl hands back — the product a url names, and the variant
15993
+ * its own ?variant= selected. */
15994
+ interface ShopifyProductFromUrl {
15995
+ product: ShopifyProduct;
15996
+ /** The variant ?variant= named, or NULL when the url named none — the ordinary
15997
+ * case for a link off a collection page. Also null when it named one the store
15998
+ * no longer publishes, which warnings says. NEVER the first variant instead:
15999
+ * that answers "is my size in stock" about a different size. */
16000
+ variant: ShopifyVariant | null;
16001
+ /** The ?variant= value exactly as the url carried it, kept even when it
16002
+ * matched nothing — a stale link is a fact about the link. */
16003
+ variantIdInUrl: string | null;
16004
+ /** Empty on a clean resolve. One entry when the url named a variant the store
16005
+ * no longer publishes. */
16006
+ warnings: string[];
14770
16007
  }
14771
16008
  interface ShopifyCart {
14772
16009
  /** The store's own cart token — a bearer credential, so treat it like one. */
@@ -14795,10 +16032,45 @@ interface ShopifyCart {
14795
16032
 
14796
16033
  /**
14797
16034
  * Reads one product by handle — every variant, its exact price, its SKU and whether that
14798
- * specific size or colour is purchasable right now.
16035
+ * specific size or colour is purchasable right now. Pass { withReviews: true } to ALSO get the
16036
+ * star rating and review count from whichever review app the merchant installed; it is off by
16037
+ * default because it costs a second origin and usually the rendered product page too.
14799
16038
  */
14800
16039
  getProduct(handle: string): Promise<ShopifyProduct>;
14801
16040
 
16041
+ /**
16042
+ * Turns a product URL into the product, which is the address a caller actually holds when a
16043
+ * link arrives from a search result, a page or a person. Takes the whole url — origin, market
16044
+ * prefix, ?variant= and #fragment — so nothing has to be stripped down to a bare handle first,
16045
+ * and the variant the url named comes back beside the product instead of being lost. THROWS,
16046
+ * naming the domain, on a url belonging to another storefront. Takes the same { withReviews:
16047
+ * true } option as getProduct, on the same default: a url is another way of naming one
16048
+ * product, so holding a link rather than a handle must not cost a caller the rating.
16049
+ */
16050
+ resolveProductUrl(url: string): Promise<ShopifyProductFromUrl>;
16051
+
16052
+ /**
16053
+ * Reads FULL detail for many products in one call — the shape for ranking a candidate set,
16054
+ * since a search row carries neither the description copy nor the per-variant stock a ranking
16055
+ * turns on. PARTIAL by construction: a search row's handle may 404 on the Ajax product door
16056
+ * (measured 2026-08-05, 2 of 10 sampled members), so one bad handle is named in `missing` and
16057
+ * costs that row alone, where `getProduct` throws and takes the whole set with it. Capped at
16058
+ * 50 handles because each one is a request to the store. Takes the same { withReviews: true }
16059
+ * option, and is the shape to use for it: the review app's key is learned from the first pages
16060
+ * and reused, so ratings for twenty handles cost a handful of page fetches rather than twenty.
16061
+ */
16062
+ getProducts(handles: string[]): Promise<ShopifyProductBatch>;
16063
+
16064
+ /**
16065
+ * Walks the store's WHOLE catalogue a page at a time, in its own merchandised order — the
16066
+ * shape that makes a realistic candidate set reachable at all, since search ranks against a
16067
+ * query and returns one slice. The caller advances a cursor and stops on its own budget or on
16068
+ * a null cursor. There is deliberately no fetch-everything call: every row is a request
16069
+ * against a stranger's storefront, and how many candidates a ranking needs is the caller's
16070
+ * decision rather than one taken once, inside the library, on behalf of every member.
16071
+ */
16072
+ listProducts(opts?: { limit?: number; cursor?: string | null }): Promise<ShopifyProductPage>;
16073
+
14802
16074
  /**
14803
16075
  * Lists the store's own merchandised collections. THIS is where a retailer states a SET: a
14804
16076
  * collection the store itself named "Matching Sets" or "Activewear Sets" carries setLike:
@@ -14811,7 +16083,18 @@ interface ShopifyCart {
14811
16083
  * rows. An empty list is an ordinary answer — several named 'look' collections publish no
14812
16084
  * products through this door.
14813
16085
  */
14814
- getCollection(handle: string, opts?: { limit?: number }): Promise<ShopifyCollectionProducts>;
16086
+ getCollection(handle: string, opts?: { limit?: number; cursor?: string | null }): Promise<ShopifyCollectionProducts>;
16087
+
16088
+ /**
16089
+ * Two answers in one call. `evidence` is ONLY what a MERCHANDISER pinned by hand, in the order
16090
+ * they typed it — an explicit retailer statement, and [] on most products, which is the
16091
+ * retailer's own answer. `recommendations` is EVERY row the store returns including the
16092
+ * algorithmic ones, each carrying source.by (retailer | algorithm) and the store's own
16093
+ * strategy token, so a caller can use them without either field lying. The algorithmic rows
16094
+ * are usually SUBSTITUTES rather than companions. Pass includeAlgorithmic: false to skip the
16095
+ * second request.
16096
+ */
16097
+ getSetEvidence(handle: string, opts?: { includeAlgorithmic?: boolean }): Promise<ShopifySetEvidence>;
14815
16098
  }
14816
16099
  }
14817
16100
 
@@ -14823,7 +16106,10 @@ interface ShopifyCart {
14823
16106
  interface BowmarkProviders {
14824
16107
  aa: BowmarkProvider_aa.Unit;
14825
16108
  abercrombie: BowmarkProvider_abercrombie.Unit;
16109
+ aiper: BowmarkProvider_aiper.Unit;
16110
+ ajmadison: BowmarkProvider_ajmadison.Unit;
14826
16111
  ashleyfurniture: BowmarkProvider_ashleyfurniture.Unit;
16112
+ atlasseniorliving: BowmarkProvider_atlasseniorliving.Unit;
14827
16113
  avis: BowmarkProvider_avis.Unit;
14828
16114
  azure: BowmarkProvider_azure.Unit;
14829
16115
  barletta: BowmarkProvider_barletta.Unit;
@@ -14837,6 +16123,7 @@ interface BowmarkProviders {
14837
16123
  cars: BowmarkProvider_cars.Unit;
14838
16124
  cheapflights: BowmarkProvider_cheapflights.Unit;
14839
16125
  chriscraft: BowmarkProvider_chriscraft.Unit;
16126
+ classichome: BowmarkProvider_classichome.Unit;
14840
16127
  classpass: BowmarkProvider_classpass.Unit;
14841
16128
  cloudflare: BowmarkProvider_cloudflare.Unit;
14842
16129
  cyberpowerpc: BowmarkProvider_cyberpowerpc.Unit;
@@ -14845,6 +16132,7 @@ interface BowmarkProviders {
14845
16132
  dickssportinggoods: BowmarkProvider_dickssportinggoods.Unit;
14846
16133
  dillards: BowmarkProvider_dillards.Unit;
14847
16134
  discounttire: BowmarkProvider_discounttire.Unit;
16135
+ embroker: BowmarkProvider_embroker.Unit;
14848
16136
  erieinsurance: BowmarkProvider_erieinsurance.Unit;
14849
16137
  extraspace: BowmarkProvider_extraspace.Unit;
14850
16138
  firstdibs: BowmarkProvider_firstdibs.Unit;
@@ -14854,22 +16142,30 @@ interface BowmarkProviders {
14854
16142
  fred: BowmarkProvider_fred.Unit;
14855
16143
  geico: BowmarkProvider_geico.Unit;
14856
16144
  google_flights: BowmarkProvider_google_flights.Unit;
16145
+ gotchacovered: BowmarkProvider_gotchacovered.Unit;
14857
16146
  grainger: BowmarkProvider_grainger.Unit;
16147
+ handypro: BowmarkProvider_handypro.Unit;
16148
+ harmar: BowmarkProvider_harmar.Unit;
14858
16149
  hauslabs: BowmarkProvider_hauslabs.Unit;
14859
16150
  healthcare_gov: BowmarkProvider_healthcare_gov.Unit;
14860
16151
  hellofresh: BowmarkProvider_hellofresh.Unit;
14861
16152
  hellotend: BowmarkProvider_hellotend.Unit;
14862
16153
  hilton: BowmarkProvider_hilton.Unit;
16154
+ hobie: BowmarkProvider_hobie.Unit;
14863
16155
  hunter: BowmarkProvider_hunter.Unit;
14864
16156
  ibuypower: BowmarkProvider_ibuypower.Unit;
14865
16157
  insurify: BowmarkProvider_insurify.Unit;
14866
16158
  interiordefine: BowmarkProvider_interiordefine.Unit;
16159
+ islllc: BowmarkProvider_islllc.Unit;
14867
16160
  joybird: BowmarkProvider_joybird.Unit;
14868
16161
  kayak: BowmarkProvider_kayak.Unit;
16162
+ kitchentuneup: BowmarkProvider_kitchentuneup.Unit;
16163
+ kompan: BowmarkProvider_kompan.Unit;
14869
16164
  labcorp: BowmarkProvider_labcorp.Unit;
14870
16165
  linkedin: BowmarkProvider_linkedin.Unit;
14871
16166
  liquiddeath: BowmarkProvider_liquiddeath.Unit;
14872
16167
  lonelyplanet: BowmarkProvider_lonelyplanet.Unit;
16168
+ louvershop: BowmarkProvider_louvershop.Unit;
14873
16169
  lufthansa: BowmarkProvider_lufthansa.Unit;
14874
16170
  lululemon: BowmarkProvider_lululemon.Unit;
14875
16171
  maidenhome: BowmarkProvider_maidenhome.Unit;
@@ -14916,6 +16212,7 @@ interface BowmarkProviders {
14916
16212
  visible: BowmarkProvider_visible.Unit;
14917
16213
  walmart: BowmarkProvider_walmart.Unit;
14918
16214
  wellfound: BowmarkProvider_wellfound.Unit;
16215
+ yourarborhome: BowmarkProvider_yourarborhome.Unit;
14919
16216
  "000de82": BowmarkFamily_shopify_store.Unit;
14920
16217
  "001r3iv0": BowmarkFamily_shopify_store.Unit;
14921
16218
  "00246d8e": BowmarkFamily_shopify_store.Unit;
@@ -51946,6 +53243,7 @@ interface BowmarkProviders {
51946
53243
  santabarbaraforgeandiron: BowmarkFamily_shopify_store.Unit;
51947
53244
  santabarbaranutrients: BowmarkFamily_shopify_store.Unit;
51948
53245
  santaclararealtorstore: BowmarkFamily_shopify_store.Unit;
53246
+ santacruzbicycles: BowmarkFamily_shopify_store.Unit;
51949
53247
  santacruzmountainsclothing: BowmarkFamily_shopify_store.Unit;
51950
53248
  santafesoapranch: BowmarkFamily_shopify_store.Unit;
51951
53249
  santafewineandchilefiesta: BowmarkFamily_shopify_store.Unit;
@@ -66645,5 +67943,6 @@ interface BowmarkLibrary {
66645
67943
  music: BowmarkCapability_music.Unit;
66646
67944
  pcparts: BowmarkCapability_pcparts.Unit;
66647
67945
  read: BowmarkCapability_read.Unit;
67946
+ sheds: BowmarkCapability_sheds.Unit;
66648
67947
  providers: BowmarkProviders;
66649
67948
  }