doordash-cli 0.2.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 ADDED
@@ -0,0 +1,47 @@
1
+ # Changelog
2
+
3
+ All notable changes to `doordash-cli` will be documented in this file.
4
+
5
+ - Versions follow [Semantic Versioning](https://semver.org/).
6
+ - Release entries are generated from squash-merged conventional commits on `main`.
7
+ - Git tags use the `vX.Y.Z` form.
8
+ - Historical `doordash-cli-vX.Y.Z` tags are bridged locally during release automation.
9
+
10
+ See [docs/releasing.md](docs/releasing.md) for the maintainer release flow.
11
+
12
+ ## 0.2.0 (2026-03-11)
13
+
14
+ ### Features
15
+
16
+ * add direct DoorDash consumer API transport ([06b5194](https://github.com/LatencyTDH/doordash-cli/commit/06b5194f72e05547574b169cf9a56311309bb722))
17
+ * add read-only existing-order tracking ([#9](https://github.com/LatencyTDH/doordash-cli/issues/9)) ([a4695e1](https://github.com/LatencyTDH/doordash-cli/commit/a4695e16fa7e267e265f5e4f5c3d5585657dc337)), closes [#6](https://github.com/LatencyTDH/doordash-cli/issues/6)
18
+ * finish direct address enrollment and standalone nested add-ons ([#2](https://github.com/LatencyTDH/doordash-cli/issues/2)) ([d4a2f6a](https://github.com/LatencyTDH/doordash-cli/commit/d4a2f6a29b7dd407ee4444c78cb6af41f3981bc0))
19
+ * ship direct session import and configurable item payloads ([360707c](https://github.com/LatencyTDH/doordash-cli/commit/360707c3504d19e424284eb4ac4ac8f7914e9031))
20
+ * streamline install and npm packaging ([#11](https://github.com/LatencyTDH/doordash-cli/issues/11)) ([d6cec89](https://github.com/LatencyTDH/doordash-cli/commit/d6cec89ca5f4f239309ea48052072fc9ca944e5d))
21
+
22
+ ### Bug Fixes
23
+
24
+ * bridge legacy release tags during release-it migration ([#22](https://github.com/LatencyTDH/doordash-cli/issues/22)) ([0360d90](https://github.com/LatencyTDH/doordash-cli/commit/0360d90a9f1fe0e42a61cc2a34051d714fd2891d))
25
+ * keep release-please on the pre-v1 track ([7af23fa](https://github.com/LatencyTDH/doordash-cli/commit/7af23fa94301642f84802a791fa6f0b53d60371a))
26
+ * make linked cli entrypoints usable ([6cd18e4](https://github.com/LatencyTDH/doordash-cli/commit/6cd18e4a9e8165d60cda5bc064aebb6b99b8bf20))
27
+ * pin release-please initial version to 0.1.0 ([a679966](https://github.com/LatencyTDH/doordash-cli/commit/a679966ab51f9693eafecfe7fce9123b862d0aec))
28
+ * reset direct cart id when switching stores ([4f0db85](https://github.com/LatencyTDH/doordash-cli/commit/4f0db85de3c1fe554cf4b479b2f225b7f1bf88cf))
29
+ * validate cart-safe command surface ([f7bcb6c](https://github.com/LatencyTDH/doordash-cli/commit/f7bcb6cc41e6869de1dcbb3fb20f3e6052156efe))
30
+
31
+ ## 0.1.0 (2026-03-10)
32
+
33
+ ### Features
34
+
35
+ * add direct DoorDash consumer API transport ([06b5194](https://github.com/LatencyTDH/doordash-cli/commit/06b5194f72e05547574b169cf9a56311309bb722))
36
+ * add read-only existing-order tracking ([#9](https://github.com/LatencyTDH/doordash-cli/issues/9)) ([a4695e1](https://github.com/LatencyTDH/doordash-cli/commit/a4695e16fa7e267e265f5e4f5c3d5585657dc337))
37
+ * finish direct address enrollment and standalone nested add-ons ([#2](https://github.com/LatencyTDH/doordash-cli/issues/2)) ([d4a2f6a](https://github.com/LatencyTDH/doordash-cli/commit/d4a2f6a29b7dd407ee4444c78cb6af41f3981bc0))
38
+ * ship direct session import and configurable item payloads ([360707c](https://github.com/LatencyTDH/doordash-cli/commit/360707c3504d19e424284eb4ac4ac8f7914e9031))
39
+ * streamline install and npm packaging ([#11](https://github.com/LatencyTDH/doordash-cli/issues/11)) ([d6cec89](https://github.com/LatencyTDH/doordash-cli/commit/d6cec89ca5f4f239309ea48052072fc9ca944e5d))
40
+
41
+ ### Bug Fixes
42
+
43
+ * keep release-please on the pre-v1 track ([7af23fa](https://github.com/LatencyTDH/doordash-cli/commit/7af23fa94301642f84802a791fa6f0b53d60371a))
44
+ * make linked cli entrypoints usable ([6cd18e4](https://github.com/LatencyTDH/doordash-cli/commit/6cd18e4a9e8165d60cda5bc064aebb6b99b8bf20))
45
+ * pin release-please initial version to 0.1.0 ([a679966](https://github.com/LatencyTDH/doordash-cli/commit/a679966ab51f9693eafecfe7fce9123b862d0aec))
46
+ * reset direct cart id when switching stores ([4f0db85](https://github.com/LatencyTDH/doordash-cli/commit/4f0db85de3c1fe554cf4b479b2f225b7f1bf88cf))
47
+ * validate cart-safe command surface ([f7bcb6c](https://github.com/LatencyTDH/doordash-cli/commit/f7bcb6cc41e6869de1dcbb3fb20f3e6052156efe))
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 doordash-cli contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # doordash-cli
2
+
3
+ > Cart-safe DoorDash CLI for terminal workflows.
4
+
5
+ `doordash-cli` is an unofficial CLI for the parts of DoorDash that work well in a shell: sign in once, set a delivery address, search restaurants, inspect menus and items, read existing orders, and manage a cart with JSON output.
6
+
7
+ It stops before checkout.
8
+
9
+ ## Highlights
10
+
11
+ - **Cart-safe by design** — browse, inspect existing orders, and manage a cart; no checkout, payment, or order mutation.
12
+ - **Direct API first** — auth, discovery, existing-order, and cart commands use DoorDash consumer-web GraphQL/HTTP rather than DOM clicking.
13
+ - **JSON-friendly** — every command prints structured output.
14
+ - **Fail-closed** — unsupported commands, flags, or unsafe payload shapes are rejected.
15
+
16
+ ## Install
17
+
18
+ ### Install from npm
19
+
20
+ ```bash
21
+ npm install -g doordash-cli
22
+ ```
23
+
24
+ That installs both lowercase command names: `doordash-cli` and `dd-cli`.
25
+
26
+ Package page: <https://www.npmjs.com/package/doordash-cli>
27
+
28
+ For the full install and first-run guide, see [docs/install.md](docs/install.md).
29
+
30
+ ### Install from source instead
31
+
32
+ If you want the latest unreleased work or a local checkout you can edit, use:
33
+
34
+ ```bash
35
+ git clone https://github.com/LatencyTDH/doordash-cli.git
36
+ cd doordash-cli
37
+ npm install
38
+ npm link
39
+ ```
40
+
41
+ If you prefer to run from a checkout without linking:
42
+
43
+ ```bash
44
+ npm run cli -- --help
45
+ ```
46
+
47
+ ### Browser prerequisite
48
+
49
+ If you plan to use `auth-bootstrap`, install Playwright's Chromium build once:
50
+
51
+ ```bash
52
+ doordash-cli install-browser
53
+ # or, from a checkout without linking
54
+ npm run install:browser
55
+ ```
56
+
57
+ ## First run
58
+
59
+ ```bash
60
+ doordash-cli auth-bootstrap
61
+ doordash-cli auth-check
62
+ doordash-cli set-address --address "350 5th Ave, New York, NY 10118"
63
+ doordash-cli search --query sushi
64
+ ```
65
+
66
+ If you are running from a checkout without `npm link`, replace `doordash-cli` with `npm run cli --`.
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.
69
+
70
+ ## Command surface
71
+
72
+ ### Session
73
+
74
+ - `install-browser`
75
+ - `auth-check`
76
+ - `auth-bootstrap`
77
+ - `auth-clear`
78
+
79
+ ### Discovery
80
+
81
+ - `set-address --address <text>`
82
+ - `search --query <text> [--cuisine <name>]`
83
+ - `menu --restaurant-id <id>`
84
+ - `item --restaurant-id <id> --item-id <id>`
85
+
86
+ ### Existing orders
87
+
88
+ - `orders [--limit <n>] [--active-only]`
89
+ - `order --order-id <id>`
90
+
91
+ ### Cart
92
+
93
+ - `add-to-cart --restaurant-id <id> (--item-id <id> | --item-name <name>)`
94
+ - `update-cart --cart-item-id <id> --quantity <n>`
95
+ - `cart`
96
+
97
+ For configurable items and working command examples, see [docs/examples.md](docs/examples.md).
98
+
99
+ ## Safety
100
+
101
+ The CLI allowlists browse, existing-order, and cart commands. It hard-blocks:
102
+
103
+ - `checkout`
104
+ - `place-order`
105
+ - payment actions
106
+ - order mutation or cancellation actions
107
+
108
+ Safety is enforced in code:
109
+
110
+ - unsupported commands hard-fail
111
+ - unknown flags are rejected before DoorDash work runs
112
+ - direct cart mutations use validated request shapes
113
+ - unsupported nested option transports fail closed
114
+
115
+ ## Docs
116
+
117
+ - [Install guide](docs/install.md)
118
+ - [Examples](docs/examples.md)
119
+ - [Release process](docs/releasing.md)
120
+ - `man dd-cli`
121
+ - `man doordash-cli`
122
+
123
+ ## Caveats
124
+
125
+ - This is an unofficial integration against DoorDash consumer-web traffic.
126
+ - DoorDash can change request shapes, anti-bot behavior, or session handling at any time.
127
+ - Review results before trusting them for anything important.
128
+
129
+ ## License
130
+
131
+ MIT
package/dist/bin.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/bin.js ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ import { runCli } from "./cli.js";
3
+ void runCli();
package/dist/cli.d.ts ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env node
2
+ export declare function version(): string;
3
+ export declare function usage(): string;
4
+ export declare function normalizeOptionToken(token: string): string;
5
+ export declare function parseArgv(argv: string[]): {
6
+ command?: string;
7
+ flags: Record<string, string>;
8
+ };
9
+ export declare function main(argv?: string[]): Promise<void>;
10
+ export declare function runCli(argv?: string[]): Promise<void>;
11
+ export declare function isDirectExecution(argv1?: string | undefined, metaUrl?: string): boolean;
package/dist/cli.js ADDED
@@ -0,0 +1,180 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync, realpathSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const UNICODE_DASH_PREFIX = /^[\u2012\u2013\u2014\u2015\u2212]+/u;
6
+ const FLAG_BODY = /^[A-Za-z0-9][A-Za-z0-9-]*(=.*)?$/;
7
+ const PACKAGE_JSON_PATH = new URL("../package.json", import.meta.url);
8
+ const PACKAGE_VERSION = JSON.parse(readFileSync(PACKAGE_JSON_PATH, "utf8")).version;
9
+ export function version() {
10
+ return PACKAGE_VERSION;
11
+ }
12
+ export function usage() {
13
+ return [
14
+ `doordash-cli v${version()}`,
15
+ "",
16
+ "Usage:",
17
+ " dd-cli <command> [flags]",
18
+ " doordash-cli <command> [flags]",
19
+ "",
20
+ "Meta:",
21
+ " --help, -h",
22
+ " --version, -v",
23
+ "",
24
+ "Safe commands:",
25
+ " install-browser",
26
+ " auth-check",
27
+ " auth-bootstrap",
28
+ " auth-clear",
29
+ ' set-address --address "350 5th Ave, New York, NY 10118"',
30
+ " search --query sushi [--cuisine japanese]",
31
+ " menu --restaurant-id 123456",
32
+ " item --restaurant-id 123456 --item-id 7890",
33
+ " orders [--limit 20] [--active-only]",
34
+ " order --order-id 3f4c6d0e-1234-5678-90ab-cdef12345678",
35
+ " add-to-cart --restaurant-id 123456 (--item-id 7890 | --item-name \"Spicy Tuna Roll\") [--quantity 2] [--special-instructions \"no wasabi\"] [--options-json '[{\"groupId\":\"703393388\",\"optionId\":\"4716032529\"}]']",
36
+ " update-cart --cart-item-id abc123 --quantity 2",
37
+ " cart",
38
+ "",
39
+ "Notes:",
40
+ " - Run with no arguments to show this help.",
41
+ " - Common Unicode long dashes are normalized for flags, so —help / –help work too.",
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.",
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
+ " - auth-check can reuse an already-signed-in compatible DoorDash browser session when one is available.",
47
+ " - set-address now uses DoorDash autocomplete/get-or-create plus addConsumerAddressV2 for brand-new address enrollment when needed.",
48
+ " - configurable items require explicit --options-json selections.",
49
+ ' - 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"}]}].',
50
+ " - other non-recommended nested cursor trees still fail closed until DoorDash exposes a directly provable transport.",
51
+ "",
52
+ "Out-of-scope commands remain intentionally unsupported:",
53
+ " checkout, place-order, payment actions, order mutation/cancellation",
54
+ "",
55
+ "Examples:",
56
+ " dd-cli --help",
57
+ " dd-cli install-browser",
58
+ " dd-cli search --query sushi",
59
+ " dd-cli orders --active-only",
60
+ " doordash-cli order --order-id 3f4c6d0e-1234-5678-90ab-cdef12345678",
61
+ " doordash-cli auth-check",
62
+ "",
63
+ "Allowed commands: install-browser, auth-check, auth-bootstrap, auth-clear, set-address, search, menu, item, orders, order, add-to-cart, update-cart, cart",
64
+ ].join("\n");
65
+ }
66
+ export function normalizeOptionToken(token) {
67
+ const prefix = token.match(UNICODE_DASH_PREFIX)?.[0];
68
+ if (!prefix) {
69
+ return token;
70
+ }
71
+ const body = token.slice(prefix.length);
72
+ if (!FLAG_BODY.test(body)) {
73
+ return token;
74
+ }
75
+ return `${body.length === 1 ? "-" : "--"}${body}`;
76
+ }
77
+ function looksLikeFlagToken(token) {
78
+ return token.startsWith("-") || normalizeOptionToken(token) !== token;
79
+ }
80
+ export function parseArgv(argv) {
81
+ const tokens = [...argv];
82
+ const flags = {};
83
+ let command;
84
+ if (tokens[0] !== undefined && !looksLikeFlagToken(tokens[0])) {
85
+ command = tokens.shift();
86
+ }
87
+ for (let i = 0; i < tokens.length; i += 1) {
88
+ const rawToken = tokens[i];
89
+ if (rawToken === undefined) {
90
+ throw new Error("Unexpected empty argument");
91
+ }
92
+ const token = normalizeOptionToken(rawToken);
93
+ if (token === "-h" || token === "--help") {
94
+ flags.help = "true";
95
+ continue;
96
+ }
97
+ if (token === "-v" || token === "--version") {
98
+ flags.version = "true";
99
+ continue;
100
+ }
101
+ if (!token.startsWith("--")) {
102
+ throw new Error(`Unexpected positional argument: ${rawToken}`);
103
+ }
104
+ const inlineEquals = token.indexOf("=");
105
+ if (inlineEquals !== -1) {
106
+ const key = token.slice(2, inlineEquals);
107
+ if (!key) {
108
+ throw new Error("Empty flag name");
109
+ }
110
+ flags[key] = token.slice(inlineEquals + 1);
111
+ continue;
112
+ }
113
+ const key = token.slice(2);
114
+ if (!key) {
115
+ throw new Error("Empty flag name");
116
+ }
117
+ const next = tokens[i + 1];
118
+ if (next === undefined || looksLikeFlagToken(next)) {
119
+ flags[key] = "true";
120
+ continue;
121
+ }
122
+ flags[key] = next;
123
+ i += 1;
124
+ }
125
+ return { command, flags };
126
+ }
127
+ export async function main(argv = process.argv.slice(2)) {
128
+ const { command, flags } = parseArgv(argv);
129
+ if (flags.version === "true") {
130
+ console.log(version());
131
+ return;
132
+ }
133
+ if (!command || command === "help" || flags.help === "true") {
134
+ console.log(usage());
135
+ return;
136
+ }
137
+ const lib = await import("./lib.js");
138
+ lib.assertSafeCommand(command);
139
+ const safeCommand = command;
140
+ try {
141
+ const result = await lib.runCommand(safeCommand, flags);
142
+ console.log(JSON.stringify(result, null, 2));
143
+ process.exitCode = 0;
144
+ }
145
+ finally {
146
+ await lib.shutdown();
147
+ }
148
+ }
149
+ export async function runCli(argv = process.argv.slice(2)) {
150
+ try {
151
+ await main(argv);
152
+ }
153
+ catch (error) {
154
+ console.error(error instanceof Error ? error.message : String(error));
155
+ console.error(`\n${usage()}`);
156
+ try {
157
+ const { shutdown } = await import("./lib.js");
158
+ await shutdown().catch(() => { });
159
+ }
160
+ catch {
161
+ // Help/no-arg flows should work even if runtime deps are unavailable.
162
+ }
163
+ process.exitCode = 1;
164
+ }
165
+ }
166
+ export function isDirectExecution(argv1 = process.argv[1], metaUrl = import.meta.url) {
167
+ if (!argv1) {
168
+ return false;
169
+ }
170
+ const modulePath = fileURLToPath(metaUrl);
171
+ try {
172
+ return realpathSync(resolve(argv1)) === realpathSync(modulePath);
173
+ }
174
+ catch {
175
+ return resolve(argv1) === resolve(modulePath);
176
+ }
177
+ }
178
+ if (isDirectExecution()) {
179
+ void runCli();
180
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,154 @@
1
+ import test from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { chmodSync, readFileSync } from "node:fs";
4
+ import { mkdtemp, rm, symlink } from "node:fs/promises";
5
+ import { tmpdir } from "node:os";
6
+ import { dirname, join } from "node:path";
7
+ import { fileURLToPath } from "node:url";
8
+ import { spawnSync } from "node:child_process";
9
+ import { SAFE_COMMANDS, assertAllowedFlags, assertSafeCommand } from "./lib.js";
10
+ import { parseArgv, version } from "./cli.js";
11
+ const distDir = dirname(fileURLToPath(import.meta.url));
12
+ const binPath = join(distDir, "bin.js");
13
+ function runCli(args) {
14
+ return spawnSync(process.execPath, [binPath, ...args], {
15
+ encoding: "utf8",
16
+ });
17
+ }
18
+ async function runLinkedCli(linkName, args) {
19
+ chmodSync(binPath, 0o755);
20
+ const tempDir = await mkdtemp(join(tmpdir(), "doordash-cli-"));
21
+ const linkPath = join(tempDir, linkName);
22
+ try {
23
+ await symlink(binPath, linkPath);
24
+ return spawnSync(linkPath, args, {
25
+ encoding: "utf8",
26
+ });
27
+ }
28
+ finally {
29
+ await rm(tempDir, { recursive: true, force: true });
30
+ }
31
+ }
32
+ test("safe command allowlist stays cart-safe while adding install helpers", () => {
33
+ assert.deepEqual(SAFE_COMMANDS, [
34
+ "install-browser",
35
+ "auth-check",
36
+ "auth-bootstrap",
37
+ "auth-clear",
38
+ "set-address",
39
+ "search",
40
+ "menu",
41
+ "item",
42
+ "orders",
43
+ "order",
44
+ "add-to-cart",
45
+ "update-cart",
46
+ "cart",
47
+ ]);
48
+ });
49
+ test("dangerous and unsupported legacy commands are rejected", () => {
50
+ assert.throws(() => assertSafeCommand("checkout"), /Blocked command: checkout/);
51
+ assert.throws(() => assertSafeCommand("place-order"), /Blocked command: place-order/);
52
+ assert.throws(() => assertSafeCommand("track-order"), /Use `orders` or `order --order-id/);
53
+ assert.throws(() => assertSafeCommand("payment"), /Blocked command: payment/);
54
+ });
55
+ test("unsupported flags are rejected before network work runs", () => {
56
+ assert.throws(() => assertAllowedFlags("cart", { payment: "visa" }), /Unsupported flag\(s\) for cart/);
57
+ assert.throws(() => assertAllowedFlags("item", { query: "salmon" }), /Unsupported flag\(s\) for item/);
58
+ assert.throws(() => assertAllowedFlags("orders", { cuisine: "japanese" }), /Unsupported flag\(s\) for orders/);
59
+ });
60
+ test("argument parsing supports inline, spaced, and meta flags", () => {
61
+ assert.deepEqual(parseArgv(["search", "--query=sushi", "--cuisine", "japanese", "--version"]), {
62
+ command: "search",
63
+ flags: {
64
+ query: "sushi",
65
+ cuisine: "japanese",
66
+ version: "true",
67
+ },
68
+ });
69
+ assert.deepEqual(parseArgv(["-h", "-v"]), {
70
+ command: undefined,
71
+ flags: {
72
+ help: "true",
73
+ version: "true",
74
+ },
75
+ });
76
+ assert.deepEqual(parseArgv(["orders", "--limit=5", "--active-only"]), {
77
+ command: "orders",
78
+ flags: {
79
+ limit: "5",
80
+ "active-only": "true",
81
+ },
82
+ });
83
+ });
84
+ test("argument parsing normalizes common Unicode dash flags", () => {
85
+ assert.deepEqual(parseArgv(["—help"]), {
86
+ command: undefined,
87
+ flags: { help: "true" },
88
+ });
89
+ assert.deepEqual(parseArgv(["search", "—query=sushi", "–cuisine", "japanese"]), {
90
+ command: "search",
91
+ flags: {
92
+ query: "sushi",
93
+ cuisine: "japanese",
94
+ },
95
+ });
96
+ });
97
+ test("help output shows the direct read-only/cart-safe command surface", () => {
98
+ const result = runCli(["--help"]);
99
+ assert.equal(result.status, 0);
100
+ assert.match(result.stdout, new RegExp(`doordash-cli v${version().replace(".", "\\.")}`));
101
+ assert.match(result.stdout, /Usage:/);
102
+ assert.match(result.stdout, /dd-cli <command>/);
103
+ assert.match(result.stdout, /doordash-cli <command>/);
104
+ assert.match(result.stdout, /install-browser/);
105
+ assert.match(result.stdout, /auth-bootstrap/);
106
+ assert.match(result.stdout, /set-address --address/);
107
+ assert.match(result.stdout, /orders \[--limit 20\] \[--active-only\]/);
108
+ assert.match(result.stdout, /order --order-id/);
109
+ assert.match(result.stdout, /options-json/);
110
+ assert.match(result.stdout, /--version, -v/);
111
+ assert.match(result.stdout, /man dd-cli/);
112
+ assert.match(result.stdout, /Out-of-scope commands remain intentionally unsupported/);
113
+ assert.doesNotMatch(result.stdout, /Dd-cli/);
114
+ });
115
+ test("repository ships man pages for the supported lowercase command names", () => {
116
+ const ddManPath = join(distDir, "..", "man", "dd-cli.1");
117
+ const aliasManPath = join(distDir, "..", "man", "doordash-cli.1");
118
+ assert.match(readFileSync(ddManPath, "utf8"), /install-browser/);
119
+ assert.doesNotMatch(readFileSync(ddManPath, "utf8"), /Dd-cli/);
120
+ assert.equal(readFileSync(aliasManPath, "utf8").trim(), ".so man1/dd-cli.1");
121
+ });
122
+ test("-h and no-arg invocation both show usage", () => {
123
+ const shortHelp = runCli(["-h"]);
124
+ assert.equal(shortHelp.status, 0);
125
+ assert.match(shortHelp.stdout, /Usage:/);
126
+ const noArgs = runCli([]);
127
+ assert.equal(noArgs.status, 0);
128
+ assert.match(noArgs.stdout, /Usage:/);
129
+ assert.match(noArgs.stdout, /Run with no arguments to show this help/);
130
+ });
131
+ test("symlinked entrypoints print help for supported lowercase command names", async () => {
132
+ for (const commandName of ["dd-cli", "doordash-cli"]) {
133
+ const result = await runLinkedCli(commandName, ["--help"]);
134
+ assert.equal(result.status, 0, `${commandName} should exit 0`);
135
+ assert.match(result.stdout, /Usage:/, `${commandName} should print usage`);
136
+ assert.match(result.stdout, /dd-cli <command>/, `${commandName} should print command help`);
137
+ }
138
+ });
139
+ test("version flag prints the package version", () => {
140
+ const result = runCli(["--version"]);
141
+ assert.equal(result.status, 0);
142
+ assert.equal(result.stdout.trim(), version());
143
+ });
144
+ test("blocked commands fail immediately", () => {
145
+ const result = runCli(["checkout"]);
146
+ assert.equal(result.status, 1);
147
+ assert.match(result.stderr, /Blocked command: checkout/);
148
+ assert.match(result.stderr, /existing-order inspection only/);
149
+ });
150
+ test("safe commands reject unknown flags before touching DoorDash flows", () => {
151
+ const result = runCli(["cart", "--payment-method", "visa"]);
152
+ assert.equal(result.status, 1);
153
+ assert.match(result.stderr, /Unsupported flag\(s\) for cart: payment-method/);
154
+ });