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 +19 -0
- package/README.md +14 -3
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +16 -8
- package/dist/cli.test.js +41 -3
- package/dist/direct-api.d.ts +39 -6
- package/dist/direct-api.js +439 -76
- package/dist/direct-api.test.js +313 -14
- package/dist/lib.js +0 -2
- package/dist/session-storage.d.ts +4 -0
- package/dist/session-storage.js +20 -0
- package/dist/session-storage.test.d.ts +1 -0
- package/dist/session-storage.test.js +12 -0
- package/docs/examples.md +3 -3
- package/docs/install.md +15 -5
- package/man/dd-cli.1 +26 -14
- package/package.json +1 -2
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
|
-
###
|
|
48
|
+
### Optional runtime bootstrap
|
|
48
49
|
|
|
49
|
-
If
|
|
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
|
-
|
|
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
|
|
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
|
-
" -
|
|
46
|
-
" - login
|
|
47
|
-
" - auth-check can quietly reuse
|
|
48
|
-
" -
|
|
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
|
-
|
|
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 =
|
|
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);
|
package/dist/direct-api.d.ts
CHANGED
|
@@ -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
|
|
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 {};
|