GitHub - jonastemplestein/waitrose

3 min read Original article ↗

waitrose

Unofficial CLI and API client for Waitrose grocery shopping.

Quick Start

Install

bun add -g waitrose   # CLI (global install)
bun add waitrose      # Library

CLI Usage

waitrose login          # Login (prompts for email/password)
waitrose trolley        # View trolley
waitrose search "milk"  # Search products
waitrose add <line-number> [quantity]
waitrose help           # All commands

Library Usage

The library exports a WaitroseClient class and all TypeScript types.

Basic Setup

import WaitroseClient from "waitrose";

const client = new WaitroseClient();
await client.login("email@example.com", "password");

Search Products

// Simple search
const results = await client.searchProducts("milk");
console.log(`Found ${results.totalMatches} products`);

for (const product of results.products) {
  console.log(`${product.name} - ${product.displayPrice} (${product.lineNumber})`);
}

// Search with options
const sorted = await client.searchProducts("bread", {
  size: 20,
  sortBy: "PRICE_LOW_2_HIGH",
});

// Paginated search
const page1 = await client.searchProductsPage("cheese", 1, 10);
const page2 = await client.searchProductsPage("cheese", 2, 10);

// Browse by category
const bakery = await client.browseProducts("groceries/bakery");

Trolley Operations

// Get current trolley
const trolley = await client.getTrolley();
console.log(`Items in trolley: ${trolley.trolley.trolleyItems.length}`);

// Add item to trolley (lineNumber, quantity)
await client.addToTrolley("088903", 2);

// Update item with note
await client.updateTrolleyItems([{
  lineNumber: "088903",
  quantity: { amount: 3, uom: "C62" },
  noteToShopper: "Ripe ones please",
  canSubstitute: true,
}]);

// Remove item
await client.removeFromTrolley("088903");

// Empty trolley
await client.emptyTrolley();

Orders

// Get all orders
const { pending, previous } = await client.getOrders(10);

// Get specific order with full details
const order = await client.getOrder("WEB-123456789");
console.log(`Order status: ${order.status}`);
console.log(`Total: £${order.totals.estimated?.totalPrice?.amount}`);

// Get product names for order lines
const lineNumbers = order.orderLines.map(line => line.lineNumber);
const products = await client.getProductsByLineNumbers(lineNumbers);

// Cancel an order
await client.cancelOrder("WEB-123456789");

Delivery Slots

// Get current booked slot
const currentSlot = await client.getCurrentSlot();
if (currentSlot?.startDateTime) {
  console.log(`Booked: ${currentSlot.startDateTime}`);
}

// Get available slot dates
const dates = await client.getSlotDates("DELIVERY");

// Get slots for a specific date
const slotDays = await client.getSlotDays("DELIVERY", dates[0].id);
for (const day of slotDays) {
  for (const slot of day.slots) {
    if (slot.status === "AVAILABLE") {
      console.log(`${slot.startDateTime} - £${slot.charge?.amount}`);
    }
  }
}

// Book a slot
await client.bookSlot(slotId, "DELIVERY", addressId);

Account Info

// Get account profile and memberships
const { profile, memberships } = await client.getAccountInfo();
console.log(`Email: ${profile.email}`);
console.log(`Address: ${profile.contactAddress?.line1}`);

// Get shopping context
const context = await client.getShoppingContext();
console.log(`Customer ID: ${context.customerId}`);
console.log(`Order ID: ${context.customerOrderId}`);

Campaigns & Promotions

const campaigns = await client.getCampaigns();
for (const campaign of campaigns) {
  console.log(`${campaign.name}: ${campaign.startDate} - ${campaign.endDate}`);
}

Session Management

// Check if authenticated
if (client.isAuthenticated()) {
  console.log(`Customer: ${client.getCustomerId()}`);
  console.log(`Order: ${client.getOrderId()}`);
}

// Logout
await client.logout();

TypeScript Types

All types are exported for use in your application:

import type {
  // Core types
  Session,
  ShoppingContext,
  Price,
  Quantity,
  
  // Trolley
  TrolleyResponse,
  TrolleyItem,
  TrolleyProduct,
  TrolleyItemInput,
  
  // Orders
  Order,
  OrderDetails,
  OrderLine,
  
  // Slots
  CurrentSlot,
  SlotDate,
  SlotDay,
  SlotType,
  BookSlotResult,
  
  // Search
  SearchProduct,
  SearchResponse,
  SearchSortBy,
  SearchQueryParams,
  ProductDetail,
  
  // Account
  AccountProfile,
  Membership,
  
  // Other
  Campaign,
  UnitOfMeasure,
  ApiFailure,
} from "waitrose";

Authentication

The bearer token expires after 15 minutes. For best results, set environment variables—the CLI will automatically re-authenticate when needed:

export WAITROSE_USERNAME=your@email.com
export WAITROSE_PASSWORD=yourpassword

For library usage, you can re-authenticate when needed:

try {
  await client.getTrolley();
} catch (err) {
  // Token expired, re-authenticate
  await client.login(username, password);
  await client.getTrolley();
}

Alternatively, run waitrose login which prompts for credentials and stores the token locally.

Config

Credentials stored in ~/.waitrose/config.json

License

MIT