doordash-cli 0.3.3 → 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.
@@ -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, hasDoorDashCookies, normalizeItemName, parseExistingOrderLifecycleStatus, parseExistingOrdersResponse, parseOptionSelectionsJson, parseSearchRestaurantRow, resolveAvailableAddressMatch, selectManagedBrowserImportMode, } from "./direct-api.js";
3
+ import { bootstrapAuthSessionWithDeps, buildAddConsumerAddressPayload, buildAddToCartPayload, buildUpdateCartPayload, extractExistingOrdersFromApolloCache, normalizeItemName, parseExistingOrderLifecycleStatus, parseExistingOrdersResponse, parseOptionSelectionsJson, parseSearchRestaurantRow, resolveAttachedBrowserCdpCandidates, resolveAvailableAddressMatch, resolveSystemBrowserOpenCommand, selectAttachedBrowserImportMode, } from "./direct-api.js";
4
4
  function configurableItemDetail() {
5
5
  return {
6
6
  success: true,
@@ -144,25 +144,324 @@ 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");
147
+ test("resolveAttachedBrowserCdpCandidates prioritizes explicit envs, compatibility envs, config, and defaults", () => {
148
+ const env = {
149
+ DOORDASH_BROWSER_CDP_URLS: "http://127.0.0.1:9555/, http://127.0.0.1:9556",
150
+ DOORDASH_ATTACHED_BROWSER_CDP_URL: "http://127.0.0.1:9666/",
151
+ DOORDASH_BROWSER_CDP_PORTS: "9333, 9334",
152
+ DOORDASH_BROWSER_CDP_PORT: "9444",
153
+ OPENCLAW_BROWSER_CDP_URL: "http://127.0.0.1:18888/",
154
+ };
155
+ const candidates = resolveAttachedBrowserCdpCandidates(env, ["http://127.0.0.1:9777"]);
156
+ assert.deepEqual(candidates.slice(0, 7), [
157
+ "http://127.0.0.1:9555",
158
+ "http://127.0.0.1:9556",
159
+ "http://127.0.0.1:9666",
160
+ "http://127.0.0.1:9333",
161
+ "http://127.0.0.1:9334",
162
+ "http://127.0.0.1:9444",
163
+ "http://127.0.0.1:18888",
164
+ ]);
165
+ assert.ok(candidates.includes("http://127.0.0.1:9777"));
166
+ assert.ok(candidates.includes("http://127.0.0.1:18792"));
167
+ assert.ok(candidates.includes("http://127.0.0.1:18800"));
168
+ assert.ok(candidates.includes("http://127.0.0.1:9222"));
151
169
  });
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");
170
+ test("resolveSystemBrowserOpenCommand stays generic across operating systems", () => {
171
+ assert.deepEqual(resolveSystemBrowserOpenCommand("https://www.doordash.com/home", "darwin"), {
172
+ command: "open",
173
+ args: ["https://www.doordash.com/home"],
174
+ });
175
+ assert.deepEqual(resolveSystemBrowserOpenCommand("https://www.doordash.com/home", "linux"), {
176
+ command: "xdg-open",
177
+ args: ["https://www.doordash.com/home"],
178
+ });
179
+ assert.deepEqual(resolveSystemBrowserOpenCommand("https://www.doordash.com/home", "win32"), {
180
+ command: "cmd",
181
+ args: ["/c", "start", "", "https://www.doordash.com/home"],
182
+ });
158
183
  });
159
- test("managed browser import skips unrelated browser contexts", () => {
160
- assert.equal(hasDoorDashCookies([{ domain: ".github.com" }]), false);
161
- assert.equal(selectManagedBrowserImportMode({
184
+ test("selectAttachedBrowserImportMode treats an authenticated browser with DoorDash cookies as an immediate import candidate", () => {
185
+ assert.equal(selectAttachedBrowserImportMode({
186
+ pageUrls: ["https://github.com/LatencyTDH/doordash-cli/pulls"],
187
+ cookies: [{ domain: ".doordash.com" }],
188
+ }), "cookies");
189
+ assert.equal(selectAttachedBrowserImportMode({
190
+ pageUrls: ["https://www.doordash.com/home"],
191
+ cookies: [{ domain: ".github.com" }],
192
+ }), "page");
193
+ assert.equal(selectAttachedBrowserImportMode({
162
194
  pageUrls: ["https://github.com/LatencyTDH/doordash-cli"],
163
195
  cookies: [{ domain: ".github.com" }],
164
196
  }), "skip");
165
197
  });
198
+ test("bootstrapAuthSessionWithDeps returns immediately when saved local auth is already valid", async () => {
199
+ const auth = {
200
+ success: true,
201
+ isLoggedIn: true,
202
+ email: "user@example.com",
203
+ firstName: "Test",
204
+ lastName: "User",
205
+ consumerId: "consumer-1",
206
+ marketId: "market-1",
207
+ defaultAddress: null,
208
+ cookiesPath: "/tmp/cookies.json",
209
+ storageStatePath: "/tmp/storage-state.json",
210
+ };
211
+ let importCalls = 0;
212
+ let openCalls = 0;
213
+ let waitCalls = 0;
214
+ let markCalls = 0;
215
+ const logs = [];
216
+ const result = await bootstrapAuthSessionWithDeps({
217
+ clearBlockedBrowserImport: async () => { },
218
+ checkPersistedAuth: async () => auth,
219
+ importBrowserSessionIfAvailable: async () => {
220
+ importCalls += 1;
221
+ return true;
222
+ },
223
+ markBrowserImportAttempted: () => {
224
+ markCalls += 1;
225
+ },
226
+ getAttachedBrowserCdpCandidates: async () => {
227
+ throw new Error("should not inspect candidates when saved auth is already valid");
228
+ },
229
+ getReachableCdpCandidates: async () => {
230
+ throw new Error("should not probe reachability when saved auth is already valid");
231
+ },
232
+ openUrlInAttachedBrowser: async () => {
233
+ throw new Error("should not try to open an attached browser when saved auth is already valid");
234
+ },
235
+ openUrlInDefaultBrowser: async () => {
236
+ openCalls += 1;
237
+ return true;
238
+ },
239
+ waitForAttachedBrowserSessionImport: async () => {
240
+ waitCalls += 1;
241
+ return true;
242
+ },
243
+ waitForManagedBrowserLogin: async () => {
244
+ throw new Error("should not launch a managed browser when saved auth is already valid");
245
+ },
246
+ checkAuthDirect: async () => {
247
+ throw new Error("should not re-check auth through the live session when saved auth is already valid");
248
+ },
249
+ log: (message) => {
250
+ logs.push(message);
251
+ },
252
+ });
253
+ assert.equal(importCalls, 0);
254
+ assert.equal(markCalls, 0);
255
+ assert.equal(openCalls, 0);
256
+ assert.equal(waitCalls, 0);
257
+ assert.equal(logs.length, 0);
258
+ assert.equal(result.isLoggedIn, true);
259
+ assert.match(result.message, /Already signed in with saved local DoorDash session state/);
260
+ });
261
+ test("bootstrapAuthSessionWithDeps returns immediately when an attached browser session is already authenticated", async () => {
262
+ const auth = {
263
+ success: true,
264
+ isLoggedIn: true,
265
+ email: "user@example.com",
266
+ firstName: "Test",
267
+ lastName: "User",
268
+ consumerId: "consumer-1",
269
+ marketId: "market-1",
270
+ defaultAddress: null,
271
+ cookiesPath: "/tmp/cookies.json",
272
+ storageStatePath: "/tmp/storage-state.json",
273
+ };
274
+ let openCalls = 0;
275
+ let waitCalls = 0;
276
+ let markCalls = 0;
277
+ const logs = [];
278
+ const result = await bootstrapAuthSessionWithDeps({
279
+ clearBlockedBrowserImport: async () => { },
280
+ checkPersistedAuth: async () => null,
281
+ importBrowserSessionIfAvailable: async () => true,
282
+ markBrowserImportAttempted: () => {
283
+ markCalls += 1;
284
+ },
285
+ getAttachedBrowserCdpCandidates: async () => {
286
+ throw new Error("should not inspect candidates on immediate browser-session import");
287
+ },
288
+ getReachableCdpCandidates: async () => {
289
+ throw new Error("should not probe reachability on immediate browser-session import");
290
+ },
291
+ openUrlInAttachedBrowser: async () => {
292
+ throw new Error("should not open a browser when immediate browser-session import succeeded");
293
+ },
294
+ openUrlInDefaultBrowser: async () => {
295
+ openCalls += 1;
296
+ return true;
297
+ },
298
+ waitForAttachedBrowserSessionImport: async () => {
299
+ waitCalls += 1;
300
+ return true;
301
+ },
302
+ waitForManagedBrowserLogin: async () => {
303
+ throw new Error("should not launch a managed browser when immediate browser-session import succeeded");
304
+ },
305
+ checkAuthDirect: async () => auth,
306
+ log: (message) => {
307
+ logs.push(message);
308
+ },
309
+ });
310
+ assert.equal(markCalls, 1);
311
+ assert.equal(openCalls, 0);
312
+ assert.equal(waitCalls, 0);
313
+ assert.equal(logs.length, 0);
314
+ assert.equal(result.isLoggedIn, true);
315
+ assert.match(result.message, /Imported an existing signed-in browser session/);
316
+ });
317
+ test("bootstrapAuthSessionWithDeps opens a watchable attached browser session before entering the full wait path", async () => {
318
+ const auth = {
319
+ success: true,
320
+ isLoggedIn: true,
321
+ email: "user@example.com",
322
+ firstName: "Test",
323
+ lastName: "User",
324
+ consumerId: "consumer-1",
325
+ marketId: "market-1",
326
+ defaultAddress: null,
327
+ cookiesPath: "/tmp/cookies.json",
328
+ storageStatePath: "/tmp/storage-state.json",
329
+ };
330
+ let openAttachedCalls = 0;
331
+ let openDefaultCalls = 0;
332
+ let waitCalls = 0;
333
+ let reachableCalls = 0;
334
+ let waitTimeoutMs = 0;
335
+ const logs = [];
336
+ const result = await bootstrapAuthSessionWithDeps({
337
+ clearBlockedBrowserImport: async () => { },
338
+ checkPersistedAuth: async () => null,
339
+ importBrowserSessionIfAvailable: async () => false,
340
+ markBrowserImportAttempted: () => { },
341
+ getAttachedBrowserCdpCandidates: async () => ["http://127.0.0.1:9222"],
342
+ getReachableCdpCandidates: async (candidates) => {
343
+ reachableCalls += 1;
344
+ return candidates;
345
+ },
346
+ openUrlInAttachedBrowser: async () => {
347
+ openAttachedCalls += 1;
348
+ return true;
349
+ },
350
+ openUrlInDefaultBrowser: async () => {
351
+ openDefaultCalls += 1;
352
+ return true;
353
+ },
354
+ waitForAttachedBrowserSessionImport: async (input) => {
355
+ waitCalls += 1;
356
+ waitTimeoutMs = input.timeoutMs;
357
+ return true;
358
+ },
359
+ waitForManagedBrowserLogin: async () => {
360
+ throw new Error("should not launch a managed browser when an attached browser is reachable");
361
+ },
362
+ checkAuthDirect: async () => auth,
363
+ log: (message) => {
364
+ logs.push(message);
365
+ },
366
+ });
367
+ assert.equal(reachableCalls, 1);
368
+ assert.equal(openAttachedCalls, 1);
369
+ assert.equal(openDefaultCalls, 0);
370
+ assert.equal(waitCalls, 1);
371
+ assert.equal(waitTimeoutMs, 180_000);
372
+ assert.match(logs.join("\n"), /Opened DoorDash in the reusable browser session I'm watching/);
373
+ assert.match(logs.join("\n"), /Detected 1 reusable browser connection/);
374
+ assert.match(result.message, /saved it for direct API use/);
375
+ });
376
+ test("bootstrapAuthSessionWithDeps falls back to a managed browser login window when no reusable browser connection is discoverable", async () => {
377
+ const auth = {
378
+ success: true,
379
+ isLoggedIn: true,
380
+ email: "user@example.com",
381
+ firstName: "Test",
382
+ lastName: "User",
383
+ consumerId: "consumer-1",
384
+ marketId: "market-1",
385
+ defaultAddress: null,
386
+ cookiesPath: "/tmp/cookies.json",
387
+ storageStatePath: "/tmp/storage-state.json",
388
+ };
389
+ let managedCalls = 0;
390
+ let attachedWaitCalls = 0;
391
+ const logs = [];
392
+ const result = await bootstrapAuthSessionWithDeps({
393
+ clearBlockedBrowserImport: async () => { },
394
+ checkPersistedAuth: async () => null,
395
+ importBrowserSessionIfAvailable: async () => false,
396
+ markBrowserImportAttempted: () => { },
397
+ getAttachedBrowserCdpCandidates: async () => ["http://127.0.0.1:9222"],
398
+ getReachableCdpCandidates: async () => [],
399
+ openUrlInAttachedBrowser: async () => false,
400
+ openUrlInDefaultBrowser: async () => true,
401
+ waitForAttachedBrowserSessionImport: async () => {
402
+ attachedWaitCalls += 1;
403
+ return false;
404
+ },
405
+ waitForManagedBrowserLogin: async () => {
406
+ managedCalls += 1;
407
+ return auth;
408
+ },
409
+ checkAuthDirect: async () => auth,
410
+ log: (message) => {
411
+ logs.push(message);
412
+ },
413
+ });
414
+ assert.equal(managedCalls, 1);
415
+ assert.equal(attachedWaitCalls, 0);
416
+ assert.match(logs.join("\n"), /temporary Chromium login window/);
417
+ assert.match(result.message, /temporary Chromium login window/);
418
+ assert.equal(result.success, true);
419
+ assert.equal(result.isLoggedIn, true);
420
+ });
421
+ test("bootstrapAuthSessionWithDeps falls back to quick troubleshooting guidance when the managed browser login window cannot launch", async () => {
422
+ const auth = {
423
+ success: true,
424
+ isLoggedIn: false,
425
+ email: null,
426
+ firstName: null,
427
+ lastName: null,
428
+ consumerId: null,
429
+ marketId: null,
430
+ defaultAddress: null,
431
+ cookiesPath: "/tmp/cookies.json",
432
+ storageStatePath: "/tmp/storage-state.json",
433
+ };
434
+ let attachedWaitCalls = 0;
435
+ let attachedWaitTimeoutMs = 0;
436
+ const logs = [];
437
+ const result = await bootstrapAuthSessionWithDeps({
438
+ clearBlockedBrowserImport: async () => { },
439
+ checkPersistedAuth: async () => null,
440
+ importBrowserSessionIfAvailable: async () => false,
441
+ markBrowserImportAttempted: () => { },
442
+ getAttachedBrowserCdpCandidates: async () => ["http://127.0.0.1:9222"],
443
+ getReachableCdpCandidates: async () => [],
444
+ openUrlInAttachedBrowser: async () => false,
445
+ openUrlInDefaultBrowser: async () => true,
446
+ waitForAttachedBrowserSessionImport: async (input) => {
447
+ attachedWaitCalls += 1;
448
+ attachedWaitTimeoutMs = input.timeoutMs;
449
+ return false;
450
+ },
451
+ waitForManagedBrowserLogin: async () => null,
452
+ checkAuthDirect: async () => auth,
453
+ log: (message) => {
454
+ logs.push(message);
455
+ },
456
+ });
457
+ assert.equal(attachedWaitCalls, 1);
458
+ assert.equal(attachedWaitTimeoutMs, 10_000);
459
+ assert.match(logs.join("\n"), /couldn't launch the temporary Chromium login window/i);
460
+ assert.match(logs.join("\n"), /won't keep you waiting for the full login timeout/i);
461
+ assert.equal(result.success, false);
462
+ assert.equal(result.isLoggedIn, false);
463
+ assert.match(result.message, /still isn't exposing a reusable browser session/);
464
+ });
166
465
  test("parseOptionSelectionsJson parses structured recursive option selections", () => {
167
466
  assert.deepEqual(parseOptionSelectionsJson('[{"groupId":"703393388","optionId":"4716032529"},{"groupId":"recommended_option_546935995","optionId":"546936011","children":[{"groupId":"780057412","optionId":"4702669757","quantity":2}]}]'), [
168
467
  { groupId: "703393388", optionId: "4716032529" },
@@ -1,3 +1,4 @@
1
1
  export declare function getSessionConfigDir(): string;
2
2
  export declare function getCookiesPath(): string;
3
3
  export declare function getStorageStatePath(): string;
4
+ export declare function getBrowserImportBlockPath(): string;
@@ -5,6 +5,7 @@ import { join } from "node:path";
5
5
  const SESSION_CONFIG_DIR = join(homedir(), ".config", "striderlabs-mcp-doordash");
6
6
  const COOKIES_FILE = join(SESSION_CONFIG_DIR, "cookies.json");
7
7
  const STORAGE_STATE_FILE = join(SESSION_CONFIG_DIR, "storage-state.json");
8
+ const BROWSER_IMPORT_BLOCK_FILE = join(SESSION_CONFIG_DIR, "browser-import-blocked");
8
9
  export function getSessionConfigDir() {
9
10
  return SESSION_CONFIG_DIR;
10
11
  }
@@ -14,3 +15,6 @@ export function getCookiesPath() {
14
15
  export function getStorageStatePath() {
15
16
  return STORAGE_STATE_FILE;
16
17
  }
18
+ export function getBrowserImportBlockPath() {
19
+ return BROWSER_IMPORT_BLOCK_FILE;
20
+ }
@@ -2,10 +2,11 @@ import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import { homedir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { getCookiesPath, getSessionConfigDir, getStorageStatePath } from "./session-storage.js";
5
+ import { getBrowserImportBlockPath, getCookiesPath, getSessionConfigDir, getStorageStatePath } from "./session-storage.js";
6
6
  test("session storage paths stay compatible with the historical StriderLabs location", () => {
7
7
  const configDir = join(homedir(), ".config", "striderlabs-mcp-doordash");
8
8
  assert.equal(getSessionConfigDir(), configDir);
9
9
  assert.equal(getCookiesPath(), join(configDir, "cookies.json"));
10
10
  assert.equal(getStorageStatePath(), join(configDir, "storage-state.json"));
11
+ assert.equal(getBrowserImportBlockPath(), join(configDir, "browser-import-blocked"));
11
12
  });
package/docs/examples.md CHANGED
@@ -14,7 +14,7 @@ All commands print JSON.
14
14
 
15
15
  ## Session setup
16
16
 
17
- Install the matching Chromium build once if you do not already have it:
17
+ If your environment does not already have the bundled Playwright runtime installed, install it once:
18
18
 
19
19
  ```bash
20
20
  doordash-cli install-browser
@@ -26,13 +26,13 @@ Check whether you already have reusable session state:
26
26
  doordash-cli auth-check
27
27
  ```
28
28
 
29
- If needed, launch Chromium for a one-time sign-in and save reusable state:
29
+ If your saved local state is still valid, this exits immediately. Otherwise it tries to reuse a discoverable signed-in browser session, or opens a temporary Chromium login window and saves the session there:
30
30
 
31
31
  ```bash
32
32
  doordash-cli login
33
33
  ```
34
34
 
35
- Reset saved session state when you want a clean start:
35
+ Reset saved session state when you want a clean logged-out start. This also disables automatic browser-session reuse until your next explicit `doordash-cli login`:
36
36
 
37
37
  ```bash
38
38
  doordash-cli logout
package/docs/install.md CHANGED
@@ -31,9 +31,9 @@ npm run cli -- --help
31
31
 
32
32
  If you stay in checkout mode, replace `doordash-cli` with `npm run cli --` in the examples below.
33
33
 
34
- ## Install the browser once
34
+ ## Install the bundled runtime if needed
35
35
 
36
- If you plan to sign in with `login`, install the matching Playwright Chromium build:
36
+ If your environment does not already have Playwright's bundled Chromium runtime installed, install it once:
37
37
 
38
38
  ### Global or linked install
39
39
 
@@ -47,6 +47,8 @@ doordash-cli install-browser
47
47
  npm run install:browser
48
48
  ```
49
49
 
50
+ That runtime is used when the CLI needs a local browser, including the temporary login window fallback.
51
+
50
52
  ## First run
51
53
 
52
54
  ```bash
@@ -56,8 +58,16 @@ doordash-cli set-address --address "350 5th Ave, New York, NY 10118"
56
58
  doordash-cli search --query sushi
57
59
  ```
58
60
 
59
- ## Session reuse
61
+ ## Login and session reuse
62
+
63
+ `doordash-cli 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.
64
+
65
+ `doordash-cli auth-check` can also quietly import a discoverable signed-in browser session unless `doordash-cli logout` disabled that auto-reuse.
66
+
67
+ `doordash-cli logout` clears persisted cookies and stored browser state, then keeps automatic browser-session reuse disabled until you explicitly run `doordash-cli login` again.
68
+
69
+ ## Browser-session troubleshooting
60
70
 
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.
71
+ Normally you should not need to think about browser plumbing. If `doordash-cli login` opens a temporary Chromium window, finish signing in there and let the CLI save the session.
62
72
 
63
- If not, run `doordash-cli login` once to save reusable state for later commands.
73
+ If you expected reuse from another browser instead, make sure that browser exposes a compatible CDP endpoint, then rerun `doordash-cli login`.
package/man/dd-cli.1 CHANGED
@@ -1,4 +1,4 @@
1
- .TH DD-CLI 1 "2026-03-11" "doordash-cli 0.3.0" "User Commands"
1
+ .TH DD-CLI 1 "2026-04-10" "doordash-cli 0.3.3" "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
@@ -30,19 +30,27 @@ is installed as a lowercase alias.
30
30
  .SH COMMANDS
31
31
  .TP
32
32
  .B install-browser
33
- Install the matching Playwright Chromium build used by this package.
33
+ Install the bundled Playwright Chromium runtime used when the CLI needs a
34
+ local browser, including the temporary login-window fallback.
34
35
  .TP
35
36
  .B auth-check
36
37
  Verify whether the saved session appears authenticated. This command can also
37
- quietly reuse an already-signed-in compatible managed-browser DoorDash session
38
- when a usable CDP endpoint is available.
38
+ quietly reuse or import an already-signed-in browser session when one is
39
+ available, unless
40
+ .B logout
41
+ explicitly disabled that auto-reuse.
39
42
  .TP
40
43
  .B login
41
- Launch Chromium for a one-time manual sign-in flow, then save reusable browser
42
- state for later direct API calls.
44
+ Reuse saved local auth when possible. Otherwise try to import a discoverable
45
+ signed-in browser session, or open a temporary Chromium login window and save
46
+ that session there. If authentication is still not established,
47
+ .B login
48
+ exits non-zero.
43
49
  .TP
44
50
  .B logout
45
- Delete stored session material used by this CLI.
51
+ Delete stored session material used by this CLI and disable automatic
52
+ browser-session reuse until the next explicit
53
+ .BR login .
46
54
  .TP
47
55
  .BI "set-address --address " ADDRESS
48
56
  Resolve and persist the active delivery address. Saved addresses are reused when
@@ -152,7 +160,7 @@ dd-cli --version
152
160
  man dd-cli
153
161
  .EE
154
162
  .PP
155
- Install the matching browser build once:
163
+ Install the bundled runtime once if needed:
156
164
  .PP
157
165
  .EX
158
166
  doordash-cli install-browser
@@ -195,14 +203,18 @@ The CLI persists reusable session state under the user's configuration
195
203
  directory. The exact file layout is an implementation detail and may evolve over
196
204
  time, but the saved state is reused across runs unless cleared with
197
205
  .BR logout .
206
+ After
207
+ .BR logout ,
208
+ automatic browser-session reuse stays disabled until the next explicit
209
+ .BR login .
198
210
  .SH ENVIRONMENT
199
- .TP
200
- .B DOORDASH_MANAGED_BROWSER_CDP_URL
201
- Preferred Chrome DevTools Protocol endpoint for importing an already-signed-in
202
- compatible managed-browser session.
203
211
  .PP
204
- The CLI may also honor additional compatible CDP endpoint environment variables
205
- for integration convenience.
212
+ In the common case you should not need to configure browser plumbing manually.
213
+ For attached-browser reuse, the CLI can probe compatible CDP URLs/ports and a
214
+ few localhost defaults. If no reusable browser connection is discoverable,
215
+ login falls back to a temporary Chromium window it can watch directly. See
216
+ .I docs/install.md
217
+ for setup and troubleshooting.
206
218
  .SH EXIT STATUS
207
219
  .TP
208
220
  .B 0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doordash-cli",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
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",