doordash-cli 0.3.0 → 0.3.2

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.
package/CHANGELOG.md CHANGED
@@ -9,6 +9,14 @@ All notable changes to `doordash-cli` will be documented in this file.
9
9
 
10
10
  See [docs/releasing.md](docs/releasing.md) for the maintainer release flow.
11
11
 
12
+ ## [0.3.2](https://github.com/LatencyTDH/doordash-cli/compare/v0.3.1...v0.3.2) (2026-04-09)
13
+
14
+ ## [0.3.1](https://github.com/LatencyTDH/doordash-cli/compare/v0.3.0...v0.3.1) (2026-03-11)
15
+
16
+ ### Bug Fixes
17
+
18
+ * keep managed browser session imports quiet ([#28](https://github.com/LatencyTDH/doordash-cli/issues/28)) ([dc25e04](https://github.com/LatencyTDH/doordash-cli/commit/dc25e0444ca63c7af1c2feaa2f32ede54b1c1f98))
19
+
12
20
  ## [0.3.0](https://github.com/LatencyTDH/doordash-cli/compare/v0.2.0...v0.3.0) (2026-03-11)
13
21
 
14
22
  ### Features
package/README.md CHANGED
@@ -65,7 +65,7 @@ doordash-cli search --query sushi
65
65
 
66
66
  If you are running from a checkout without `npm link`, replace `doordash-cli` with `npm run cli --`.
67
67
 
68
- If you already have a compatible signed-in DoorDash browser session available, `auth-check` may reuse it instead of asking you to sign in again.
68
+ If you already have a compatible signed-in DoorDash managed-browser session available, `auth-check` may quietly reuse it instead of asking you to sign in again. Routine direct commands stay quiet; use `login` when you want explicit browser interaction.
69
69
 
70
70
  ## Command surface
71
71
 
package/dist/cli.js CHANGED
@@ -44,7 +44,7 @@ export function usage() {
44
44
  " - Manual pages ship with the project: man dd-cli or man doordash-cli.",
45
45
  " - Direct GraphQL/HTTP is the default path for auth-check, set-address, search, menu, item, orders, order, cart, add-to-cart, and update-cart.",
46
46
  " - login launches Chromium for a one-time manual sign-in flow and saves reusable state for later direct API calls.",
47
- " - auth-check can reuse an already-signed-in compatible DoorDash browser session when one is available.",
47
+ " - auth-check can quietly reuse an already-signed-in compatible managed-browser DoorDash session when one is available; use login for explicit browser interaction.",
48
48
  " - set-address now uses DoorDash autocomplete/get-or-create plus addConsumerAddressV2 for brand-new address enrollment when needed.",
49
49
  " - configurable items require explicit --options-json selections.",
50
50
  ' - standalone recommended add-ons that open a nested cursor step are supported via children, e.g. [{"groupId":"recommended_option_546935995","optionId":"546936011","children":[{"groupId":"780057412","optionId":"4702669757"}]}].',
@@ -1,3 +1,4 @@
1
+ import { type Cookie } from "playwright";
1
2
  export type AuthResult = {
2
3
  success: true;
3
4
  isLoggedIn: boolean;
@@ -521,6 +522,12 @@ export declare function resolveAvailableAddressMatch(input: {
521
522
  printableAddress: string | null;
522
523
  source: SetAddressResult["matchedAddressSource"];
523
524
  } | null;
525
+ export declare function isDoorDashUrl(value: string): boolean;
526
+ export declare function hasDoorDashCookies(cookies: ReadonlyArray<Pick<Cookie, "domain">>): boolean;
527
+ export declare function selectManagedBrowserImportMode(input: {
528
+ pageUrls: readonly string[];
529
+ cookies: ReadonlyArray<Pick<Cookie, "domain">>;
530
+ }): "page" | "cookies" | "skip";
524
531
  export declare function parseSearchRestaurants(body: unknown[]): SearchRestaurantResult[];
525
532
  export declare function parseSearchRestaurantRow(entry: unknown): SearchRestaurantResult | null;
526
533
  export declare function parseExistingOrderLifecycleStatus(orderRoot: unknown): ExistingOrderLifecycleStatus;
@@ -808,6 +808,9 @@ class DoorDashDirectSession {
808
808
  return;
809
809
  }
810
810
  this.attemptedManagedImport = true;
811
+ if (await hasPersistedSessionArtifacts()) {
812
+ return;
813
+ }
811
814
  await importManagedBrowserSessionIfAvailable().catch(() => { });
812
815
  }
813
816
  }
@@ -1535,39 +1538,69 @@ function normalizeAddressText(value) {
1535
1538
  .replace(/[.,]/g, "")
1536
1539
  .replace(/\s+/g, " ");
1537
1540
  }
1541
+ export function isDoorDashUrl(value) {
1542
+ try {
1543
+ const url = new URL(value);
1544
+ return url.hostname === "doordash.com" || url.hostname.endsWith(".doordash.com");
1545
+ }
1546
+ catch {
1547
+ return false;
1548
+ }
1549
+ }
1550
+ export function hasDoorDashCookies(cookies) {
1551
+ return cookies.some((cookie) => {
1552
+ const domain = cookie.domain.trim().replace(/^\./, "").toLowerCase();
1553
+ return domain === "doordash.com" || domain.endsWith(".doordash.com");
1554
+ });
1555
+ }
1556
+ export function selectManagedBrowserImportMode(input) {
1557
+ if (input.pageUrls.some((url) => isDoorDashUrl(url))) {
1558
+ return "page";
1559
+ }
1560
+ if (hasDoorDashCookies(input.cookies)) {
1561
+ return "cookies";
1562
+ }
1563
+ return "skip";
1564
+ }
1538
1565
  async function importManagedBrowserSessionIfAvailable() {
1539
1566
  for (const cdpUrl of await getManagedBrowserCdpCandidates()) {
1540
1567
  if (!(await isCdpEndpointReachable(cdpUrl))) {
1541
1568
  continue;
1542
1569
  }
1543
1570
  let browser = null;
1544
- let tempPage = null;
1545
1571
  try {
1546
1572
  browser = await chromium.connectOverCDP(cdpUrl);
1547
1573
  const context = browser.contexts()[0];
1548
1574
  if (!context) {
1549
1575
  continue;
1550
1576
  }
1551
- let page = context.pages().find((candidate) => candidate.url().includes("doordash.com")) ?? null;
1552
- if (!page) {
1553
- tempPage = await context.newPage();
1554
- page = tempPage;
1555
- await page.goto(`${BASE_URL}/home`, { waitUntil: "domcontentloaded", timeout: 90_000 }).catch(() => { });
1556
- await page.waitForTimeout(1_000);
1557
- }
1558
- const consumerData = await fetchConsumerViaPage(page);
1559
- const consumer = consumerData.consumer ?? null;
1560
- if (!consumer || consumer.isGuest !== false) {
1577
+ const cookies = await context.cookies();
1578
+ const pages = context.pages();
1579
+ const reusablePage = pages.find((candidate) => isDoorDashUrl(candidate.url())) ?? null;
1580
+ const importMode = selectManagedBrowserImportMode({
1581
+ pageUrls: reusablePage ? [reusablePage.url()] : [],
1582
+ cookies,
1583
+ });
1584
+ if (importMode === "skip") {
1561
1585
  continue;
1562
1586
  }
1563
- await saveContextState(context);
1564
- return true;
1587
+ if (reusablePage) {
1588
+ const consumerData = await fetchConsumerViaPage(reusablePage).catch(() => null);
1589
+ const consumer = consumerData?.consumer ?? null;
1590
+ if (consumer?.isGuest === false) {
1591
+ await saveContextState(context, cookies);
1592
+ return true;
1593
+ }
1594
+ }
1595
+ if (hasDoorDashCookies(cookies)) {
1596
+ await saveContextState(context, cookies);
1597
+ return true;
1598
+ }
1565
1599
  }
1566
1600
  catch {
1567
1601
  continue;
1568
1602
  }
1569
1603
  finally {
1570
- await tempPage?.close().catch(() => { });
1571
1604
  await browser?.close().catch(() => { });
1572
1605
  }
1573
1606
  }
@@ -1588,12 +1621,12 @@ async function fetchConsumerViaPage(page) {
1588
1621
  });
1589
1622
  return parseGraphQlResponse("managedBrowserConsumerImport", raw.status, raw.text);
1590
1623
  }
1591
- async function saveContextState(context) {
1624
+ async function saveContextState(context, cookies = null) {
1592
1625
  const storageStatePath = getStorageStatePath();
1593
1626
  await ensureConfigDir();
1594
1627
  await context.storageState({ path: storageStatePath });
1595
- const cookies = await context.cookies();
1596
- await writeFile(getCookiesPath(), JSON.stringify(cookies, null, 2));
1628
+ const resolvedCookies = cookies ?? (await context.cookies());
1629
+ await writeFile(getCookiesPath(), JSON.stringify(resolvedCookies, null, 2));
1597
1630
  }
1598
1631
  async function getManagedBrowserCdpCandidates() {
1599
1632
  const candidates = new Set();
@@ -2282,6 +2315,12 @@ async function hasStorageState() {
2282
2315
  return false;
2283
2316
  }
2284
2317
  }
2318
+ async function hasPersistedSessionArtifacts() {
2319
+ if (await hasStorageState()) {
2320
+ return true;
2321
+ }
2322
+ return (await readStoredCookies()).length > 0;
2323
+ }
2285
2324
  async function readStoredCookies() {
2286
2325
  try {
2287
2326
  const raw = await readFile(getCookiesPath(), "utf8");
@@ -1,6 +1,6 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { buildAddConsumerAddressPayload, buildAddToCartPayload, buildUpdateCartPayload, extractExistingOrdersFromApolloCache, normalizeItemName, parseExistingOrderLifecycleStatus, parseExistingOrdersResponse, parseOptionSelectionsJson, parseSearchRestaurantRow, resolveAvailableAddressMatch, } from "./direct-api.js";
3
+ import { buildAddConsumerAddressPayload, buildAddToCartPayload, buildUpdateCartPayload, extractExistingOrdersFromApolloCache, hasDoorDashCookies, normalizeItemName, parseExistingOrderLifecycleStatus, parseExistingOrdersResponse, parseOptionSelectionsJson, parseSearchRestaurantRow, resolveAvailableAddressMatch, selectManagedBrowserImportMode, } from "./direct-api.js";
4
4
  function configurableItemDetail() {
5
5
  return {
6
6
  success: true,
@@ -144,6 +144,25 @@ test("parseSearchRestaurantRow extracts restaurant metadata from facet rows", ()
144
144
  url: "https://www.doordash.com/store/24633898/?pickup=false",
145
145
  });
146
146
  });
147
+ test("managed browser import falls back to cookie reuse without opening a DoorDash page", () => {
148
+ const cookies = [{ domain: ".doordash.com" }];
149
+ assert.equal(hasDoorDashCookies(cookies), true);
150
+ assert.equal(selectManagedBrowserImportMode({ pageUrls: [], cookies }), "cookies");
151
+ });
152
+ test("managed browser import prefers an existing DoorDash page when one is already open", () => {
153
+ const cookies = [{ domain: ".doordash.com" }];
154
+ assert.equal(selectManagedBrowserImportMode({
155
+ pageUrls: ["https://github.com/LatencyTDH/doordash-cli/pulls", "https://www.doordash.com/home"],
156
+ cookies,
157
+ }), "page");
158
+ });
159
+ test("managed browser import skips unrelated browser contexts", () => {
160
+ assert.equal(hasDoorDashCookies([{ domain: ".github.com" }]), false);
161
+ assert.equal(selectManagedBrowserImportMode({
162
+ pageUrls: ["https://github.com/LatencyTDH/doordash-cli"],
163
+ cookies: [{ domain: ".github.com" }],
164
+ }), "skip");
165
+ });
147
166
  test("parseOptionSelectionsJson parses structured recursive option selections", () => {
148
167
  assert.deepEqual(parseOptionSelectionsJson('[{"groupId":"703393388","optionId":"4716032529"},{"groupId":"recommended_option_546935995","optionId":"546936011","children":[{"groupId":"780057412","optionId":"4702669757","quantity":2}]}]'), [
149
168
  { groupId: "703393388", optionId: "4716032529" },
package/docs/install.md CHANGED
@@ -58,6 +58,6 @@ doordash-cli search --query sushi
58
58
 
59
59
  ## Session reuse
60
60
 
61
- If you already have a compatible signed-in DoorDash browser session available, direct commands may reuse it instead of opening a fresh browser context.
61
+ If you already have a compatible signed-in DoorDash managed-browser session available, direct commands may quietly reuse it instead of opening or navigating a DoorDash tab.
62
62
 
63
63
  If not, run `doordash-cli login` once to save reusable state for later commands.
package/man/dd-cli.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH DD-CLI 1 "2026-03-11" "doordash-cli 0.2.0" "User Commands"
1
+ .TH DD-CLI 1 "2026-03-11" "doordash-cli 0.3.0" "User Commands"
2
2
  .SH NAME
3
3
  dd-cli, doordash-cli \- unofficial cart-safe DoorDash CLI for browsing, read-only existing-order inspection, and cart management
4
4
  .SH SYNOPSIS
@@ -34,8 +34,8 @@ Install the matching Playwright Chromium build used by this package.
34
34
  .TP
35
35
  .B auth-check
36
36
  Verify whether the saved session appears authenticated. This command can also
37
- import an already-signed-in compatible managed-browser DoorDash session when a
38
- usable CDP endpoint is available.
37
+ quietly reuse an already-signed-in compatible managed-browser DoorDash session
38
+ when a usable CDP endpoint is available.
39
39
  .TP
40
40
  .B login
41
41
  Launch Chromium for a one-time manual sign-in flow, then save reusable browser
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doordash-cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "Cart-safe DoorDash CLI with direct API support for browse, read-only existing-order, and cart workflows.",
5
5
  "type": "module",
6
6
  "main": "dist/lib.js",