@merchbase/cli 1.0.2 → 3.0.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
@@ -1,5 +1,25 @@
1
1
  # MerchBase CLI Changelog
2
2
 
3
+ ## v3.0.0 - 2026-06-15
4
+
5
+ - Added `merchbase auth status`, `merchbase auth set`, `merchbase auth set --stdin`, and `merchbase auth clear` so local users and desktop agents can use macOS Keychain auth while automation can keep using `MERCHBASE_API_KEY`.
6
+ - Changed missing-auth guidance to point operators at `merchbase auth set` for local setup and `MERCHBASE_API_KEY` for CI, scripts, and agent runtimes.
7
+ - Changed `merchbase config show` to report the active auth source without printing secrets.
8
+ - Changed config management to use explicit key/value commands: `config get`, `config set`, `config unset`, and `config reset`.
9
+ - Removed `config clear`; use `merchbase config reset`.
10
+
11
+ ## v2.0.0 - 2026-06-10
12
+
13
+ - Product lookup now accepts ASINs copied from sales records, and product marketplace filters match concrete seller marketplaces case-insensitively.
14
+ - Help now includes high-value examples for products, designs, sales, dashboard snapshots, and planner status.
15
+ - Top-level `--help` output now uses the same curated help topics as `merchbase help`, so common command groups are easier to discover.
16
+ - `merchbase health` now verifies auth, resolved account context, and sync readiness.
17
+ - CLI config no longer stores a default account; API keys resolve the account, with marketplace override still available.
18
+ - The CLI is now read-oriented for seller and agent analysis. Server-side mutation commands are no longer exposed for product sync, sales sync, Merch status-count sync, planner enable/disable, design facet updates, or facet classification.
19
+ - `merchbase ingestion planner state` remains available as a read-only planner status command; planner controls and ripcord reset remain dashboard actions.
20
+ - `merchbase setup` commands are no longer exposed; account discovery and metadata setup are handled by the automatic planner.
21
+ - Missing API keys now return an explicit auth error instead of a generic internal error.
22
+
3
23
  ## v1.0.2 - 2026-04-02
4
24
 
5
25
  - Setup help now adapts to your saved account and current setup status, so `merchbase --help` and sync command help point to the next missing step instead of showing the same static checklist every time.
package/README.md CHANGED
@@ -11,18 +11,36 @@ npm install -g @merchbase/cli
11
11
  ## Setup
12
12
 
13
13
  ```bash
14
- export MERCHBASE_API_KEY=mb_live_xxx
14
+ merchbase auth set
15
15
  merchbase config set base-url https://app.merchbase.co
16
- merchbase config set account <accountId> us
16
+ merchbase config set marketplace US # optional
17
+ merchbase health
18
+ ```
19
+
20
+ `merchbase auth set` stores the API key in macOS Keychain. For automation,
21
+ CI, and agent runtimes, `MERCHBASE_API_KEY` overrides the stored key:
22
+
23
+ ```bash
24
+ export MERCHBASE_API_KEY=mb_live_xxx
17
25
  ```
18
26
 
19
27
  ## Usage
20
28
 
21
29
  ```bash
22
30
  merchbase help
31
+ merchbase auth status
32
+ merchbase config show
33
+ merchbase health
23
34
  merchbase products list
35
+ merchbase products get B000000001 --marketplace us
36
+ merchbase products metadata --marketplace us
24
37
  merchbase sales summary --range 30d
38
+ merchbase ingestion planner state
25
39
  merchbase changelog
26
40
  ```
27
41
 
42
+ Product ASIN lookup accepts ASINs copied from sales records. Product
43
+ marketplace filters are case-insensitive and match concrete seller
44
+ marketplaces.
45
+
28
46
  The CLI reads release notes from the packaged `CHANGELOG.md`, so installed users can inspect the current shipped notes with `merchbase changelog`.
package/dist/auth.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { type SecureStoreStatus } from "./secure-store.js";
2
+ export declare const API_KEY_ENV_VAR = "MERCHBASE_API_KEY";
3
+ export type AuthSource = "env" | "secure-store" | "missing";
4
+ export interface CliAuthState {
5
+ apiKey?: string;
6
+ authenticated: boolean;
7
+ envVar: typeof API_KEY_ENV_VAR;
8
+ hasEnvOverride: boolean;
9
+ hasStoredApiKey: boolean;
10
+ secureStore: SecureStoreStatus;
11
+ source: AuthSource;
12
+ }
13
+ export type PublicCliAuthState = Omit<CliAuthState, "apiKey">;
14
+ export declare const resolveAuthState: () => Promise<CliAuthState>;
15
+ export declare const setStoredApiKey: (apiKey: string) => Promise<CliAuthState>;
16
+ export declare const clearStoredApiKey: () => Promise<{
17
+ auth: CliAuthState;
18
+ cleared: boolean;
19
+ }>;
20
+ export declare const toPublicAuthState: (state: CliAuthState) => PublicCliAuthState;
21
+ export declare const getMissingAuthMessage: () => string;
22
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAe,MAAM,mBAAmB,CAAC;AAExE,eAAO,MAAM,eAAe,sBAAsB,CAAC;AAEnD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,cAAc,GAAG,SAAS,CAAC;AAE5D,MAAM,WAAW,YAAY;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,EAAE,OAAO,CAAC;IACvB,MAAM,EAAE,OAAO,eAAe,CAAC;IAC/B,cAAc,EAAE,OAAO,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,iBAAiB,CAAC;IAC/B,MAAM,EAAE,UAAU,CAAC;CACtB;AAED,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;AAE9D,eAAO,MAAM,gBAAgB,QAAa,OAAO,CAAC,YAAY,CAwC7D,CAAC;AAEF,eAAO,MAAM,eAAe,GAAU,QAAQ,MAAM,0BAWnD,CAAC;AAEF,eAAO,MAAM,iBAAiB;;;EAO7B,CAAC;AAEF,eAAO,MAAM,iBAAiB,GAAI,OAAO,YAAY,KAAG,kBAOtD,CAAC;AAEH,eAAO,MAAM,qBAAqB,cACyG,CAAC"}
package/dist/auth.js ADDED
@@ -0,0 +1,76 @@
1
+ import { secureStore } from "./secure-store.js";
2
+ export const API_KEY_ENV_VAR = "MERCHBASE_API_KEY";
3
+ export const resolveAuthState = async () => {
4
+ const status = await secureStore.getStatus();
5
+ const envApiKey = resolveEnvApiKey();
6
+ if (envApiKey) {
7
+ return {
8
+ apiKey: envApiKey,
9
+ authenticated: true,
10
+ envVar: API_KEY_ENV_VAR,
11
+ hasEnvOverride: true,
12
+ hasStoredApiKey: status.configured,
13
+ secureStore: status,
14
+ source: "env",
15
+ };
16
+ }
17
+ if (status.available && status.configured) {
18
+ const storedApiKey = await secureStore.readApiKey();
19
+ if (storedApiKey) {
20
+ return {
21
+ apiKey: storedApiKey,
22
+ authenticated: true,
23
+ envVar: API_KEY_ENV_VAR,
24
+ hasEnvOverride: false,
25
+ hasStoredApiKey: true,
26
+ secureStore: status,
27
+ source: "secure-store",
28
+ };
29
+ }
30
+ }
31
+ return {
32
+ authenticated: false,
33
+ envVar: API_KEY_ENV_VAR,
34
+ hasEnvOverride: false,
35
+ hasStoredApiKey: status.configured,
36
+ secureStore: status,
37
+ source: "missing",
38
+ };
39
+ };
40
+ export const setStoredApiKey = async (apiKey) => {
41
+ const trimmed = apiKey.trim();
42
+ if (!trimmed) {
43
+ throw createCliError("BAD_REQUEST", "auth set requires an API key from an argument, prompt, or --stdin.");
44
+ }
45
+ await secureStore.writeApiKey(trimmed);
46
+ return resolveAuthState();
47
+ };
48
+ export const clearStoredApiKey = async () => {
49
+ const cleared = await secureStore.clearApiKey();
50
+ return {
51
+ auth: await resolveAuthState(),
52
+ cleared,
53
+ };
54
+ };
55
+ export const toPublicAuthState = (state) => ({
56
+ authenticated: state.authenticated,
57
+ envVar: state.envVar,
58
+ hasEnvOverride: state.hasEnvOverride,
59
+ hasStoredApiKey: state.hasStoredApiKey,
60
+ secureStore: state.secureStore,
61
+ source: state.source,
62
+ });
63
+ export const getMissingAuthMessage = () => `Missing MerchBase auth. Run \`merchbase auth set\` for local use or export ${API_KEY_ENV_VAR} for automation, CI, and agent runtimes.`;
64
+ const resolveEnvApiKey = () => {
65
+ const apiKey = process.env[API_KEY_ENV_VAR]?.trim();
66
+ if (!apiKey || apiKey === "null" || apiKey === "undefined") {
67
+ return undefined;
68
+ }
69
+ return apiKey;
70
+ };
71
+ const createCliError = (code, message) => {
72
+ const error = new Error(message);
73
+ error.code = code;
74
+ return error;
75
+ };
76
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAiCpC,eAAO,MAAM,aAAa,eAwjBzB,CAAC;AAEF,eAAO,MAAM,MAAM,GAAU,eAAmB,kBAS/C,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAqDpC,eAAO,MAAM,aAAa,eAyoBzB,CAAC;AAEF,eAAO,MAAM,MAAM,GAAU,eAAmB,kBAS/C,CAAC"}
package/dist/cli.js CHANGED
@@ -1,22 +1,32 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { createMerchbaseClient, } from "@merchbase/http-client";
3
3
  import { Command } from "commander";
4
+ import { clearStoredApiKey, getMissingAuthMessage, resolveAuthState, setStoredApiKey, toPublicAuthState, } from "./auth.js";
4
5
  import { normalizeBaseUrl } from "./base-url.js";
5
6
  import { loadCliChangelog, loadCliVersion, normalizeChangelogVersion, selectCliChangelogEntry, } from "./changelog.js";
6
- import { clearConfig, getConfigPath, loadConfig, updateConfig, } from "./config.js";
7
+ import { CONFIG_KEYS, clearConfig, getConfigPath, getConfigValue, loadConfig, parseConfigKey, setConfigValue, unsetConfigValue, } from "./config.js";
7
8
  import { renderHelp, resolveHelpTopicKey } from "./help.js";
8
9
  import { printError, printSuccess } from "./output.js";
10
+ import { readSecretFromPrompt, readSecretFromStdin } from "./secret-input.js";
9
11
  import { buildSetupHelpText } from "./setup-help.js";
10
- const API_KEY_ENV_VAR = "MERCHBASE_API_KEY";
11
12
  const require = createRequire(import.meta.url);
12
13
  const CLI_PACKAGE = require("../package.json");
13
14
  const CLI_VERSION = CLI_PACKAGE.version ?? "0.0.0";
14
15
  const HELP_FLAGS = new Set(["-h", "--help"]);
16
+ const TOP_LEVEL_HELP_TOPICS = new Set([
17
+ "auth",
18
+ "changelog",
19
+ "config",
20
+ "dashboard",
21
+ "designs",
22
+ "health",
23
+ "ingestion",
24
+ "merch-account",
25
+ "products",
26
+ "sales",
27
+ ]);
15
28
  const SETUP_TARGETS_BY_COMMAND_PATH = {
16
29
  "merch-account get": "merch-account-get",
17
- "merch-account sync": "merch-account-sync",
18
- "products sync": "products-sync",
19
- "sales sync": "sales-sync",
20
30
  };
21
31
  export const createProgram = () => {
22
32
  const program = new Command();
@@ -75,20 +85,99 @@ export const createProgram = () => {
75
85
  }));
76
86
  const configCommand = program
77
87
  .command("config")
78
- .description("Manage CLI config");
88
+ .description("Manage local CLI config")
89
+ .action(async () => {
90
+ process.stdout.write(await renderHelp("config"));
91
+ });
92
+ const authCommand = program
93
+ .command("auth")
94
+ .description("Manage CLI authentication")
95
+ .action(async () => {
96
+ process.stdout.write(await renderHelp("auth"));
97
+ });
98
+ authCommand
99
+ .command("status")
100
+ .description("Show resolved auth source")
101
+ .action(wrap(async () => toPublicAuthState(await resolveAuthState())));
102
+ authCommand
103
+ .command("set [apiKey]")
104
+ .description("Store an API key in macOS Keychain")
105
+ .option("--stdin", "Read the API key from stdin")
106
+ .action(wrap(async (apiKey, options) => {
107
+ const input = await resolveAuthSetInput({
108
+ apiKey,
109
+ stdin: Boolean(options.stdin),
110
+ });
111
+ const auth = await setStoredApiKey(input.apiKey);
112
+ return {
113
+ inputSource: input.source,
114
+ stored: true,
115
+ auth: toPublicAuthState(auth),
116
+ };
117
+ }));
118
+ authCommand
119
+ .command("clear")
120
+ .description("Remove stored Keychain auth")
121
+ .action(wrap(async () => {
122
+ const result = await clearStoredApiKey();
123
+ return {
124
+ auth: toPublicAuthState(result.auth),
125
+ cleared: result.cleared,
126
+ };
127
+ }));
79
128
  configCommand
80
129
  .command("show")
81
130
  .description("Show current config")
82
131
  .action(wrap(async () => {
83
132
  const config = await loadConfig();
84
133
  return {
134
+ auth: toPublicAuthState(await resolveAuthState()),
85
135
  path: getConfigPath(),
86
136
  config,
87
137
  };
88
138
  }));
89
139
  configCommand
90
- .command("clear")
91
- .description("Clear local config")
140
+ .command("get [key]")
141
+ .description("Get one config value")
142
+ .action(wrap(async (keyInput) => {
143
+ const key = requireConfigKey(keyInput, "get");
144
+ const config = await loadConfig();
145
+ return {
146
+ key,
147
+ path: getConfigPath(),
148
+ value: getConfigValue(config, key),
149
+ };
150
+ }));
151
+ configCommand
152
+ .command("set [key] [value]")
153
+ .description("Set one config value")
154
+ .action(wrap(async (keyInput, value) => {
155
+ const key = requireConfigKey(keyInput, "set");
156
+ const nextValue = requireConfigValue(value, key);
157
+ const config = await setConfigValue(key, key === "base-url" ? normalizeBaseUrl(nextValue) : nextValue);
158
+ return {
159
+ config,
160
+ key,
161
+ path: getConfigPath(),
162
+ saved: true,
163
+ };
164
+ }));
165
+ configCommand
166
+ .command("unset [key]")
167
+ .description("Unset one config value")
168
+ .action(wrap(async (keyInput) => {
169
+ const key = requireConfigKey(keyInput, "unset");
170
+ const config = await unsetConfigValue(key);
171
+ return {
172
+ config,
173
+ key,
174
+ path: getConfigPath(),
175
+ saved: true,
176
+ };
177
+ }));
178
+ configCommand
179
+ .command("reset")
180
+ .description("Reset local config")
92
181
  .action(wrap(async () => {
93
182
  const config = await clearConfig();
94
183
  return {
@@ -96,28 +185,41 @@ export const createProgram = () => {
96
185
  config,
97
186
  };
98
187
  }));
99
- const configSet = configCommand
100
- .command("set")
101
- .description("Set config values");
102
- configSet
103
- .command("base-url <value>")
104
- .description("Set API base URL")
105
- .action(wrap(async (value) => updateConfig({ baseUrl: normalizeBaseUrl(value) })));
106
- configSet
107
- .command("account <accountId> [marketplace]")
108
- .description("Set default account and optional marketplace")
109
- .action(wrap(async (accountId, marketplace) => updateConfig({ account: accountId, marketplace })));
110
- configSet
111
- .command("range <value>")
112
- .description("Set default range alias or date interval")
113
- .action(wrap(async (value) => updateConfig({ range: value })));
114
- const accountsCommand = program
115
- .command("accounts")
116
- .description("Accounts API commands");
117
- accountsCommand
118
- .command("get")
119
- .description("Get account details")
120
- .action(wrapWithClient(async (client) => client.accounts.get.query()));
188
+ program
189
+ .command("health")
190
+ .description("Check auth, account context, and sync readiness")
191
+ .action(wrapWithClient(async (client, config) => {
192
+ const [account, readiness, planner] = await Promise.all([
193
+ client.accounts.get.query(),
194
+ client.setup.status.query(),
195
+ client.ingestion.planner.state.query(),
196
+ ]);
197
+ const missingReadiness = readiness.steps
198
+ .filter((step) => !step.ready)
199
+ .map((step) => step.key);
200
+ return {
201
+ ok: true,
202
+ baseUrl: config.baseUrl,
203
+ marketplace: config.marketplace ?? account.marketplace,
204
+ account,
205
+ readiness: {
206
+ ready: missingReadiness.length === 0,
207
+ missing: missingReadiness,
208
+ steps: readiness.steps,
209
+ ingestionClient: planner.ingestionClient,
210
+ planner: {
211
+ enabled: planner.state.enabled,
212
+ updatedAt: planner.state.updatedAt,
213
+ due: planner.work.due,
214
+ dispatchable: planner.work.dispatchable,
215
+ active: planner.work.active,
216
+ blockedBy: planner.work.blockedBy,
217
+ nextAction: planner.work.nextAction,
218
+ },
219
+ storedData: planner.storedData,
220
+ },
221
+ };
222
+ }));
121
223
  const merchAccountCommand = program
122
224
  .command("merch-account")
123
225
  .description("Amazon Merch account API commands");
@@ -129,12 +231,25 @@ export const createProgram = () => {
129
231
  if (merchAccount) {
130
232
  return merchAccount;
131
233
  }
132
- throw createCliError("PRECONDITION_FAILED", "Merch account metadata is not available for this account. Run `merchbase merch-account sync` first.");
234
+ throw createCliError("PRECONDITION_FAILED", "Merch account metadata is not available for this account. Keep the ingestion client connected and enable automatic planning.");
133
235
  }));
134
- merchAccountCommand
135
- .command("sync")
136
- .description("Start an Amazon Merch account metadata sync")
137
- .action(wrapWithClient(async (client) => client.merchAccount.sync.mutate()));
236
+ const merchAccountStatusCountsCommand = merchAccountCommand
237
+ .command("status-counts")
238
+ .description("Amazon Merch status counts commands");
239
+ merchAccountStatusCountsCommand
240
+ .command("get")
241
+ .description("Get saved Amazon Merch status counts")
242
+ .action(wrapWithClient(async (client) => client.merchAccount.statusCounts.get.query()));
243
+ const ingestionCommand = program
244
+ .command("ingestion")
245
+ .description("Ingestion operator state commands");
246
+ const plannerCommand = ingestionCommand
247
+ .command("planner")
248
+ .description("Automatic planner commands");
249
+ plannerCommand
250
+ .command("state")
251
+ .description("Get automatic planner state")
252
+ .action(wrapWithClient(async (client) => client.ingestion.planner.state.query()));
138
253
  const productsCommand = program
139
254
  .command("products")
140
255
  .description("Products API commands");
@@ -171,7 +286,7 @@ export const createProgram = () => {
171
286
  productsCommand
172
287
  .command("get <asin>")
173
288
  .description("Get product details")
174
- .option("--marketplace <marketplace>")
289
+ .requiredOption("--marketplace <marketplace>")
175
290
  .action(wrapWithClient(async (client, _config, asin, options) => client.products.get.query({
176
291
  asin,
177
292
  marketplace: options.marketplace,
@@ -180,10 +295,8 @@ export const createProgram = () => {
180
295
  .command("metadata")
181
296
  .description("Get product dataset metadata")
182
297
  .option("--marketplace <marketplace>")
183
- .option("--asin <asin>")
184
298
  .action(wrapWithClient(async (client, _config, options) => client.products.metadata.query({
185
299
  marketplace: options.marketplace,
186
- asin: options.asin,
187
300
  })));
188
301
  const productCatalogCommand = productsCommand
189
302
  .command("catalog")
@@ -205,13 +318,6 @@ export const createProgram = () => {
205
318
  .action(wrapWithClient(async (client, _config, productType) => client.products.catalog.product.query({
206
319
  productType,
207
320
  })));
208
- productsCommand
209
- .command("sync")
210
- .description("Start a products sync")
211
- .option("--range <range>", "1d|3d|7d|30d|90d", "7d")
212
- .action(wrapWithClient(async (client, _config, options) => client.products.sync.mutate({
213
- rangePreset: options.range,
214
- })));
215
321
  const designsCommand = program
216
322
  .command("designs")
217
323
  .description("Designs API commands");
@@ -241,21 +347,6 @@ export const createProgram = () => {
241
347
  .command("get <designId>")
242
348
  .description("Get design facets")
243
349
  .action(wrapWithClient(async (client, _config, designId) => client.designs.facets.get.query({ designId })));
244
- designFacetsCommand
245
- .command("update <designId>")
246
- .description("Replace design facets")
247
- .requiredOption("--facet <facetValue...>", "Facet value in the form facet:name. Repeat flag for multiple values.")
248
- .action(wrapWithClient(async (client, _config, designId, options) => client.designs.facets.update.mutate({
249
- designId,
250
- facets: options.facet.map(parseFacetValue),
251
- })));
252
- designFacetsCommand
253
- .command("classify")
254
- .description("Queue facet classification")
255
- .requiredOption("--design-id <designIds...>")
256
- .action(wrapWithClient(async (client, _config, options) => client.designs.facets.classify.mutate({
257
- designIds: options.designId,
258
- })));
259
350
  designFacetsCommand
260
351
  .command("status <jobId>")
261
352
  .description("Get classify job status")
@@ -263,13 +354,6 @@ export const createProgram = () => {
263
354
  const salesCommand = program
264
355
  .command("sales")
265
356
  .description("Sales API commands");
266
- salesCommand
267
- .command("sync")
268
- .description("Start a sales sync")
269
- .option("--range <range>", "1d|3d|7d|30d|90d", "7d")
270
- .action(wrapWithClient(async (client, _config, options) => client.sales.sync.mutate({
271
- rangePreset: options.range,
272
- })));
273
357
  salesCommand
274
358
  .command("summary")
275
359
  .description("Get sales summary")
@@ -444,54 +528,72 @@ function wrap(handler) {
444
528
  function wrapWithClient(handler) {
445
529
  return wrap(async (...args) => {
446
530
  const config = await loadConfig();
447
- const apiKey = resolveApiKey();
448
- if (!apiKey) {
449
- throw new Error(`Missing API key. Set ${API_KEY_ENV_VAR} in your environment.`);
531
+ const auth = await resolveAuthState();
532
+ if (!auth.apiKey) {
533
+ throw createCliError("UNAUTHORIZED", getMissingAuthMessage());
450
534
  }
451
535
  const headers = {};
452
- if (config.account) {
453
- headers["x-merchbase-account"] = config.account;
454
- }
455
536
  if (config.marketplace) {
456
537
  headers["x-merchbase-marketplace"] = config.marketplace;
457
538
  }
458
539
  const client = createMerchbaseClient({
459
540
  baseUrl: config.baseUrl,
460
- apiKey,
541
+ apiKey: auth.apiKey,
461
542
  headers,
462
543
  });
463
544
  return handler(client, config, ...args);
464
545
  });
465
546
  }
466
- function parseFacetValue(value) {
467
- const separator = value.indexOf(":");
468
- if (separator < 1 || separator === value.length - 1) {
469
- throw new Error(`Invalid facet value "${value}". Expected <facet>:<name>.`);
470
- }
471
- return {
472
- facet: value.slice(0, separator),
473
- name: value.slice(separator + 1),
474
- };
475
- }
476
547
  function resolveRange(config, range) {
477
548
  return range ?? config.range;
478
549
  }
479
- function resolveApiKey() {
480
- const apiKey = process.env[API_KEY_ENV_VAR]?.trim();
481
- if (!apiKey || apiKey === "null" || apiKey === "undefined") {
482
- return undefined;
483
- }
484
- return apiKey;
485
- }
486
550
  const createCliError = (code, message) => {
487
551
  const error = new Error(message);
488
552
  error.code = code;
489
553
  return error;
490
554
  };
491
- const maybeRenderHelp = (program, argv) => {
555
+ const resolveAuthSetInput = async (input) => {
556
+ if (input.apiKey && input.stdin) {
557
+ throw createCliError("BAD_REQUEST", "Use either an API-key argument or --stdin, not both.");
558
+ }
559
+ if (input.apiKey) {
560
+ return {
561
+ apiKey: input.apiKey,
562
+ source: "argument",
563
+ };
564
+ }
565
+ if (input.stdin) {
566
+ return {
567
+ apiKey: await readSecretFromStdin(),
568
+ source: "stdin",
569
+ };
570
+ }
571
+ return {
572
+ apiKey: await readSecretFromPrompt("MerchBase API key: "),
573
+ source: "prompt",
574
+ };
575
+ };
576
+ const requireConfigKey = (value, command) => {
577
+ const key = parseConfigKey(value);
578
+ if (key) {
579
+ return key;
580
+ }
581
+ if (!value) {
582
+ throw createCliError("BAD_REQUEST", `Missing config key. Usage: merchbase config ${command} <key>${command === "set" ? " <value>" : ""}. Expected: ${CONFIG_KEYS.join(", ")}.`);
583
+ }
584
+ throw createCliError("BAD_REQUEST", `Unknown config key "${value}". Expected: ${CONFIG_KEYS.join(", ")}.`);
585
+ };
586
+ const requireConfigValue = (value, key) => {
587
+ const trimmed = value?.trim();
588
+ if (!trimmed) {
589
+ throw createCliError("BAD_REQUEST", `Missing value for config key "${key}". Usage: merchbase config set ${key} <value>.`);
590
+ }
591
+ return trimmed;
592
+ };
593
+ const maybeRenderHelp = async (program, argv) => {
492
594
  const args = argv.slice(2);
493
595
  if (args.length === 0) {
494
- return renderCommandHelp(program, []);
596
+ return await renderHelp("global");
495
597
  }
496
598
  if (args[0] === "help") {
497
599
  return null;
@@ -501,7 +603,11 @@ const maybeRenderHelp = (program, argv) => {
501
603
  return null;
502
604
  }
503
605
  const commandPath = getCommandPath(args.slice(0, helpFlagIndex));
504
- return renderCommandHelp(program, commandPath);
606
+ const topicKey = getTopLevelHelpTopic(commandPath);
607
+ if (topicKey) {
608
+ return await renderHelp(topicKey);
609
+ }
610
+ return await renderCommandHelp(program, commandPath);
505
611
  };
506
612
  const renderCommandHelp = async (program, commandPath) => {
507
613
  const command = findCommand(program, commandPath) ?? program;
@@ -544,4 +650,16 @@ const findCommand = (program, commandPath) => {
544
650
  const getSetupHelpTarget = (commandPath) => {
545
651
  return SETUP_TARGETS_BY_COMMAND_PATH[commandPath.join(" ")] ?? null;
546
652
  };
653
+ const getTopLevelHelpTopic = (commandPath) => {
654
+ if (commandPath.length === 0) {
655
+ return "global";
656
+ }
657
+ if (commandPath.length > 1) {
658
+ return null;
659
+ }
660
+ if (!TOP_LEVEL_HELP_TOPICS.has(commandPath[0] ?? "")) {
661
+ return null;
662
+ }
663
+ return resolveHelpTopicKey(commandPath);
664
+ };
547
665
  //# sourceMappingURL=cli.js.map