doordash-cli 0.3.2 → 0.4.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.
package/CHANGELOG.md CHANGED
@@ -9,8 +9,27 @@ 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.4.0](https://github.com/LatencyTDH/doordash-cli/compare/v0.3.3...v0.4.0) (2026-04-10)
13
+
14
+ ### Features
15
+
16
+ * reuse attached browser sessions for login bootstrap ([#38](https://github.com/LatencyTDH/doordash-cli/issues/38)) ([dc9410d](https://github.com/LatencyTDH/doordash-cli/commit/dc9410ddce1e72b2dce6b772787c8fb0dfa9683a))
17
+
18
+ ## [0.3.3](https://github.com/LatencyTDH/doordash-cli/compare/v0.3.2...v0.3.3) (2026-04-09)
19
+
12
20
  ## [0.3.2](https://github.com/LatencyTDH/doordash-cli/compare/v0.3.1...v0.3.2) (2026-04-09)
13
21
 
22
+ ### Dependencies
23
+
24
+ * **deps:** bump @hono/node-server from 1.19.11 to 1.19.13 ([#34](https://github.com/LatencyTDH/doordash-cli/issues/34)) ([60ae01d](https://github.com/LatencyTDH/doordash-cli/commit/60ae01d00e06dc8490cf495013a476fb6aef4964))
25
+ * **deps:** bump basic-ftp from 5.2.0 to 5.2.1 ([#35](https://github.com/LatencyTDH/doordash-cli/issues/35)) ([8796dfd](https://github.com/LatencyTDH/doordash-cli/commit/8796dfda69c2615048601858e4617c36e864d1ce))
26
+ * **deps:** bump defu from 6.1.4 to 6.1.6 ([#32](https://github.com/LatencyTDH/doordash-cli/issues/32)) ([b1746bc](https://github.com/LatencyTDH/doordash-cli/commit/b1746bc51521f95d373f83ea6cb649e3722bf4b2))
27
+ * **deps-dev:** bump handlebars from 4.7.8 to 4.7.9 ([#30](https://github.com/LatencyTDH/doordash-cli/issues/30)) ([0eb650e](https://github.com/LatencyTDH/doordash-cli/commit/0eb650e654b1feff2e764bf9204a0e52db7903fd))
28
+ * **deps:** bump hono from 4.12.7 to 4.12.12 ([#33](https://github.com/LatencyTDH/doordash-cli/issues/33)) ([21fa857](https://github.com/LatencyTDH/doordash-cli/commit/21fa8570945909f26669746c73d9279a0931f096))
29
+ * **deps:** bump lodash from 4.17.23 to 4.18.1 ([#36](https://github.com/LatencyTDH/doordash-cli/issues/36)) ([82b3fd2](https://github.com/LatencyTDH/doordash-cli/commit/82b3fd27f41a44eab33332c8b12f2da48d098713))
30
+ * **deps:** bump path-to-regexp from 8.3.0 to 8.4.0 ([#31](https://github.com/LatencyTDH/doordash-cli/issues/31)) ([b07d074](https://github.com/LatencyTDH/doordash-cli/commit/b07d07443cc8614baf4fbe0fe4b703456329cd67))
31
+ * **deps:** bump picomatch from 4.0.3 to 4.0.4 ([#29](https://github.com/LatencyTDH/doordash-cli/issues/29)) ([2c95440](https://github.com/LatencyTDH/doordash-cli/commit/2c95440c4830471ea24690faed29c556f56d2461))
32
+
14
33
  ## [0.3.1](https://github.com/LatencyTDH/doordash-cli/compare/v0.3.0...v0.3.1) (2026-03-11)
15
34
 
16
35
  ### Bug Fixes
package/README.md CHANGED
@@ -9,6 +9,7 @@ It stops before checkout.
9
9
  ## Highlights
10
10
 
11
11
  - **Cart-safe by design** — browse, inspect existing orders, and manage a cart; no checkout, payment, or order mutation.
12
+ - **Browser-first login** — `dd-cli login` reuses saved local auth or a discoverable signed-in browser session when possible, and otherwise opens a temporary login window.
12
13
  - **Direct API first** — auth, discovery, existing-order, and cart commands use DoorDash consumer-web GraphQL/HTTP rather than DOM clicking.
13
14
  - **JSON-friendly** — every command prints structured output.
14
15
  - **Fail-closed** — unsupported commands, flags, or unsafe payload shapes are rejected.
@@ -44,9 +45,9 @@ If you prefer to run from a checkout without linking:
44
45
  npm run cli -- --help
45
46
  ```
46
47
 
47
- ### Browser prerequisite
48
+ ### Optional runtime bootstrap
48
49
 
49
- If you plan to sign in with `login`, install Playwright's Chromium build once:
50
+ If your environment does not already have Playwright's bundled Chromium runtime installed, install it once:
50
51
 
51
52
  ```bash
52
53
  doordash-cli install-browser
@@ -54,6 +55,8 @@ doordash-cli install-browser
54
55
  npm run install:browser
55
56
  ```
56
57
 
58
+ That runtime is used when the CLI needs a local browser, including the temporary login window fallback.
59
+
57
60
  ## First run
58
61
 
59
62
  ```bash
@@ -65,7 +68,15 @@ doordash-cli search --query sushi
65
68
 
66
69
  If you are running from a checkout without `npm link`, replace `doordash-cli` with `npm run cli --`.
67
70
 
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.
71
+ ## Login and session reuse
72
+
73
+ `login` reuses saved local auth when it is still valid. Otherwise it tries to import a discoverable signed-in browser session. If neither is available, it opens a temporary Chromium login window and saves the session there. If authentication still is not established, `login` exits non-zero.
74
+
75
+ `auth-check` reports whether the saved state appears logged in and can quietly import a discoverable signed-in browser session unless `logout` disabled that auto-reuse.
76
+
77
+ `logout` clears persisted cookies and stored browser state, then keeps automatic browser-session reuse disabled until you explicitly run `dd-cli login` again.
78
+
79
+ If `login` opens a temporary Chromium window, finish signing in there and let the CLI save the session. If you expect reuse from another browser, make sure it exposes a compatible CDP endpoint, then rerun `dd-cli login`.
69
80
 
70
81
  ## Command surface
71
82
 
package/dist/cli.d.ts CHANGED
@@ -6,6 +6,7 @@ export declare function parseArgv(argv: string[]): {
6
6
  command?: string;
7
7
  flags: Record<string, string>;
8
8
  };
9
+ export declare function commandExitCode(command: string, result: unknown): number;
9
10
  export declare function main(argv?: string[]): Promise<void>;
10
11
  export declare function runCli(argv?: string[]): Promise<void>;
11
12
  export declare function isDirectExecution(argv1?: string | undefined, metaUrl?: string): boolean;
package/dist/cli.js CHANGED
@@ -40,15 +40,14 @@ export function usage() {
40
40
  " - Run with no arguments to show this help.",
41
41
  " - Common Unicode long dashes are normalized for flags, so —help / –help work too.",
42
42
  " - Installed command names are lowercase only: dd-cli and doordash-cli.",
43
- " - install-browser downloads the matching Playwright Chromium build used by this package.",
43
+ " - install-browser downloads the bundled Playwright Chromium runtime used when the CLI needs a local browser.",
44
44
  " - Manual pages ship with the project: man dd-cli or man doordash-cli.",
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
- " - login launches Chromium for a one-time manual sign-in flow and saves reusable state for later direct API calls.",
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
- " - set-address now uses DoorDash autocomplete/get-or-create plus addConsumerAddressV2 for brand-new address enrollment when needed.",
45
+ " - login reuses saved local auth when possible, otherwise imports a signed-in browser session or opens a temporary Chromium login window.",
46
+ " - login exits non-zero if authentication is still not established.",
47
+ " - auth-check reports saved-session status and can quietly reuse/import a signed-in browser session unless logout disabled that auto-reuse.",
48
+ " - logout clears saved session files and keeps automatic browser-session reuse off until the next login.",
49
49
  " - configurable items require explicit --options-json selections.",
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"}]}].',
51
- " - other non-recommended nested cursor trees still fail closed until DoorDash exposes a directly provable transport.",
50
+ " - unsupported option trees fail closed.",
52
51
  "",
53
52
  "Out-of-scope commands remain intentionally unsupported:",
54
53
  " checkout, place-order, payment actions, order mutation/cancellation",
@@ -125,6 +124,15 @@ export function parseArgv(argv) {
125
124
  }
126
125
  return { command, flags };
127
126
  }
127
+ export function commandExitCode(command, result) {
128
+ if (command === "login" && typeof result === "object" && result !== null) {
129
+ const authResult = result;
130
+ if (authResult.success === false || authResult.isLoggedIn === false) {
131
+ return 1;
132
+ }
133
+ }
134
+ return 0;
135
+ }
128
136
  export async function main(argv = process.argv.slice(2)) {
129
137
  const { command, flags } = parseArgv(argv);
130
138
  if (flags.version === "true") {
@@ -141,7 +149,7 @@ export async function main(argv = process.argv.slice(2)) {
141
149
  try {
142
150
  const result = await lib.runCommand(safeCommand, flags);
143
151
  console.log(JSON.stringify(result, null, 2));
144
- process.exitCode = 0;
152
+ process.exitCode = commandExitCode(safeCommand, result);
145
153
  }
146
154
  finally {
147
155
  await lib.shutdown();
package/dist/cli.test.js CHANGED
@@ -1,18 +1,19 @@
1
1
  import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
- import { chmodSync, readFileSync } from "node:fs";
3
+ import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { mkdtemp, rm, symlink } from "node:fs/promises";
5
5
  import { tmpdir } from "node:os";
6
6
  import { dirname, join } from "node:path";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { spawnSync } from "node:child_process";
9
9
  import { SAFE_COMMANDS, assertAllowedFlags, assertSafeCommand } from "./lib.js";
10
- import { parseArgv, version } from "./cli.js";
10
+ import { commandExitCode, parseArgv, version } from "./cli.js";
11
11
  const distDir = dirname(fileURLToPath(import.meta.url));
12
12
  const binPath = join(distDir, "bin.js");
13
- function runCli(args) {
13
+ function runCli(args, env) {
14
14
  return spawnSync(process.execPath, [binPath, ...args], {
15
15
  encoding: "utf8",
16
+ env: env ? { ...process.env, ...env } : process.env,
16
17
  });
17
18
  }
18
19
  async function runLinkedCli(linkName, args) {
@@ -112,9 +113,14 @@ test("help output shows the direct read-only/cart-safe command surface", () => {
112
113
  assert.match(result.stdout, /options-json/);
113
114
  assert.match(result.stdout, /--version, -v/);
114
115
  assert.match(result.stdout, /man dd-cli/);
116
+ assert.match(result.stdout, /login reuses saved local auth when possible, otherwise imports a signed-in browser session or opens a temporary Chromium login window\./);
117
+ assert.match(result.stdout, /login exits non-zero if authentication is still not established\./);
118
+ assert.match(result.stdout, /auth-check reports saved-session status and can quietly reuse\/import a signed-in browser session unless logout disabled that auto-reuse\./);
119
+ assert.match(result.stdout, /logout clears saved session files and keeps automatic browser-session reuse off until the next login\./);
115
120
  assert.match(result.stdout, /Out-of-scope commands remain intentionally unsupported/);
116
121
  assert.doesNotMatch(result.stdout, /auth-bootstrap/);
117
122
  assert.doesNotMatch(result.stdout, /auth-clear/);
123
+ assert.match(result.stdout, /temporary Chromium login window/i);
118
124
  assert.doesNotMatch(result.stdout, /Dd-cli/);
119
125
  });
120
126
  test("repository ships man pages for the supported lowercase command names", () => {
@@ -124,6 +130,8 @@ test("repository ships man pages for the supported lowercase command names", ()
124
130
  assert.match(readFileSync(ddManPath, "utf8"), /\.B login/);
125
131
  assert.doesNotMatch(readFileSync(ddManPath, "utf8"), /auth-bootstrap/);
126
132
  assert.doesNotMatch(readFileSync(ddManPath, "utf8"), /auth-clear/);
133
+ assert.match(readFileSync(ddManPath, "utf8"), /automatic\s+browser-session reuse stays disabled until the next explicit/i);
134
+ assert.match(readFileSync(ddManPath, "utf8"), /temporary Chromium.*window/i);
127
135
  assert.doesNotMatch(readFileSync(ddManPath, "utf8"), /Dd-cli/);
128
136
  assert.equal(readFileSync(aliasManPath, "utf8").trim(), ".so man1/dd-cli.1");
129
137
  });
@@ -159,6 +167,36 @@ test("legacy auth command invocations point users to login/logout", () => {
159
167
  assert.match(logoutRename.stderr, /Unsupported command: auth-clear/);
160
168
  assert.match(logoutRename.stderr, /renamed it to logout/);
161
169
  });
170
+ test("commandExitCode treats failed login results as non-zero without changing read-only auth-check semantics", () => {
171
+ assert.equal(commandExitCode("login", { success: false, isLoggedIn: false }), 1);
172
+ assert.equal(commandExitCode("login", { success: true, isLoggedIn: true }), 0);
173
+ assert.equal(commandExitCode("auth-check", { success: true, isLoggedIn: false }), 0);
174
+ });
175
+ test("logout clears persisted session artifacts in the active home directory", async () => {
176
+ const tempHome = await mkdtemp(join(tmpdir(), "doordash-cli-home-"));
177
+ const sessionDir = join(tempHome, ".config", "striderlabs-mcp-doordash");
178
+ const cookiesPath = join(sessionDir, "cookies.json");
179
+ const storageStatePath = join(sessionDir, "storage-state.json");
180
+ const browserImportBlockPath = join(sessionDir, "browser-import-blocked");
181
+ mkdirSync(sessionDir, { recursive: true });
182
+ writeFileSync(cookiesPath, JSON.stringify([{ name: "session", domain: ".doordash.com" }]));
183
+ writeFileSync(storageStatePath, JSON.stringify({ cookies: [], origins: [] }));
184
+ try {
185
+ const result = runCli(["logout"], { HOME: tempHome });
186
+ assert.equal(result.status, 0);
187
+ assert.equal(existsSync(cookiesPath), false);
188
+ assert.equal(existsSync(storageStatePath), false);
189
+ assert.equal(existsSync(browserImportBlockPath), true);
190
+ const parsed = JSON.parse(result.stdout);
191
+ assert.equal(parsed.success, true);
192
+ assert.equal(parsed.cookiesPath, cookiesPath);
193
+ assert.equal(parsed.storageStatePath, storageStatePath);
194
+ assert.match(parsed.message, /disabled until the next `dd-cli login`/);
195
+ }
196
+ finally {
197
+ await rm(tempHome, { recursive: true, force: true });
198
+ }
199
+ });
162
200
  test("blocked commands fail immediately", () => {
163
201
  const result = runCli(["checkout"]);
164
202
  assert.equal(result.status, 1);
@@ -15,9 +15,15 @@ export type AuthResult = {
15
15
  cookiesPath: string;
16
16
  storageStatePath: string;
17
17
  };
18
- export type AuthBootstrapResult = AuthResult & {
18
+ export type AuthBootstrapResult = (AuthResult & {
19
+ success: true;
20
+ isLoggedIn: true;
19
21
  message: string;
20
- };
22
+ }) | (Omit<AuthResult, "success" | "isLoggedIn"> & {
23
+ success: false;
24
+ isLoggedIn: false;
25
+ message: string;
26
+ });
21
27
  export type SearchRestaurantResult = {
22
28
  id: string;
23
29
  name: string;
@@ -456,7 +462,32 @@ type GeoAddressResponse = {
456
462
  lng?: number | null;
457
463
  } | null;
458
464
  };
465
+ type BootstrapAuthSessionDeps = {
466
+ clearBlockedBrowserImport: () => Promise<void>;
467
+ checkPersistedAuth: () => Promise<AuthResult | null>;
468
+ importBrowserSessionIfAvailable: () => Promise<boolean>;
469
+ markBrowserImportAttempted: () => void;
470
+ getAttachedBrowserCdpCandidates: () => Promise<string[]>;
471
+ getReachableCdpCandidates: (candidates: string[]) => Promise<string[]>;
472
+ openUrlInAttachedBrowser: (input: {
473
+ cdpUrl: string;
474
+ targetUrl: string;
475
+ }) => Promise<boolean>;
476
+ openUrlInDefaultBrowser: (targetUrl: string) => Promise<boolean>;
477
+ waitForAttachedBrowserSessionImport: (input: {
478
+ timeoutMs: number;
479
+ pollIntervalMs: number;
480
+ }) => Promise<boolean>;
481
+ waitForManagedBrowserLogin: (input: {
482
+ targetUrl: string;
483
+ timeoutMs: number;
484
+ pollIntervalMs: number;
485
+ }) => Promise<AuthResult | null>;
486
+ checkAuthDirect: () => Promise<AuthResult>;
487
+ log: (message: string) => void;
488
+ };
459
489
  export declare function checkAuthDirect(): Promise<AuthResult>;
490
+ export declare function bootstrapAuthSessionWithDeps(deps: BootstrapAuthSessionDeps): Promise<AuthBootstrapResult>;
460
491
  export declare function bootstrapAuthSession(): Promise<AuthBootstrapResult>;
461
492
  export declare function clearStoredSession(): Promise<{
462
493
  success: true;
@@ -522,16 +553,18 @@ export declare function resolveAvailableAddressMatch(input: {
522
553
  printableAddress: string | null;
523
554
  source: SetAddressResult["matchedAddressSource"];
524
555
  } | 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: {
556
+ export declare function selectAttachedBrowserImportMode(input: {
528
557
  pageUrls: readonly string[];
529
558
  cookies: ReadonlyArray<Pick<Cookie, "domain">>;
530
559
  }): "page" | "cookies" | "skip";
560
+ export declare function resolveAttachedBrowserCdpCandidates(env: NodeJS.ProcessEnv, configCandidates?: string[]): string[];
561
+ export declare function resolveSystemBrowserOpenCommand(targetUrl: string, targetPlatform?: NodeJS.Platform): {
562
+ command: string;
563
+ args: string[];
564
+ } | null;
531
565
  export declare function parseSearchRestaurants(body: unknown[]): SearchRestaurantResult[];
532
566
  export declare function parseSearchRestaurantRow(entry: unknown): SearchRestaurantResult | null;
533
567
  export declare function parseExistingOrderLifecycleStatus(orderRoot: unknown): ExistingOrderLifecycleStatus;
534
568
  export declare function parseExistingOrdersResponse(orderRoots: unknown[]): ExistingOrderResult[];
535
569
  export declare function extractExistingOrdersFromApolloCache(cache: Record<string, unknown> | null): ExistingOrderResult[];
536
- export declare function getStorageStatePath(): string;
537
570
  export {};