@rankwrangler/cli 0.5.0 → 0.8.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.
@@ -34,13 +34,31 @@ export const loadCliPathsOrDefault = async () => {
34
34
  return getDefaultCliPaths();
35
35
  }
36
36
  };
37
- export const clearConfig = async (paths) => {
38
- await rm(paths.configPath, { force: true });
37
+ export const resetConfig = async (paths) => {
38
+ await Promise.all([
39
+ rm(paths.configPath, { force: true }),
40
+ rm(paths.globalConfigPath, { force: true }),
41
+ paths.storageDir === paths.defaultStorageDir
42
+ ? Promise.resolve()
43
+ : rm(path.join(paths.defaultStorageDir, CONFIG_FILENAME), { force: true }),
44
+ ]);
45
+ return {
46
+ paths: getDefaultCliPaths(),
47
+ config: {},
48
+ };
39
49
  };
40
50
  export const saveConfig = async (paths, config) => {
41
51
  await mkdir(paths.storageDir, { recursive: true });
42
52
  await writeFile(paths.configPath, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
43
53
  };
54
+ export const unsetStorageDir = async (paths) => {
55
+ await rm(paths.globalConfigPath, { force: true });
56
+ const nextPaths = getDefaultCliPaths();
57
+ return {
58
+ paths: nextPaths,
59
+ config: await loadConfig(nextPaths.configPath),
60
+ };
61
+ };
44
62
  export const switchStorageDir = async ({ currentConfig, currentPaths, requestedStorageDir, }) => {
45
63
  const storageDir = normalizeStorageDir(requestedStorageDir);
46
64
  const nextPaths = buildCliPaths(storageDir);
@@ -0,0 +1,9 @@
1
+ import type { createRankWranglerClient } from '@rankwrangler/http-client';
2
+ import { type CliOptionValues } from './cli-options';
3
+ type CliFail = (code: string, message: string, details?: unknown) => never;
4
+ type CliClient = ReturnType<typeof createRankWranglerClient>;
5
+ export declare const runKeywordCommand: (command: {
6
+ verb: string;
7
+ args: string[];
8
+ }, client: CliClient, options: CliOptionValues, fail: CliFail) => Promise<unknown>;
9
+ export {};
@@ -0,0 +1,69 @@
1
+ import { parseIntegerOption } from './cli-options.js';
2
+ const KEYWORD_MARKETPLACE_ID = 'ATVPDKIKX0DER';
3
+ export const runKeywordCommand = async (command, client, options, fail) => {
4
+ if (options.refresh) {
5
+ fail('INVALID_INPUT', '--refresh is not supported for keyword reads');
6
+ }
7
+ if (command.verb === 'get') {
8
+ return await runKeywordGet(command.args, client, fail);
9
+ }
10
+ if (command.verb === 'search') {
11
+ return await runKeywordSearch(command.args, client, options, fail);
12
+ }
13
+ if (command.verb === 'history') {
14
+ return await runKeywordHistory(command.args, client, options, fail);
15
+ }
16
+ fail('UNKNOWN_COMMAND', 'Unknown Keyword command', {
17
+ command: `keyword ${command.verb}`,
18
+ });
19
+ };
20
+ const runKeywordGet = async (args, client, fail) => {
21
+ const keyword = requireKeyword(args, fail, 'keyword get');
22
+ return await client.keyword.get.query({
23
+ keyword,
24
+ marketplaceId: KEYWORD_MARKETPLACE_ID,
25
+ });
26
+ };
27
+ const runKeywordSearch = async (args, client, options, fail) => {
28
+ const text = requireKeyword(args, fail, 'keyword search');
29
+ return await client.keyword.search.query({
30
+ text,
31
+ marketplaceId: KEYWORD_MARKETPLACE_ID,
32
+ cursor: parseIntegerOption({
33
+ value: options.cursor,
34
+ optionName: 'cursor',
35
+ min: 0,
36
+ max: 1_000_000,
37
+ defaultValue: 0,
38
+ }, fail),
39
+ limit: parseIntegerOption({
40
+ value: options.limit,
41
+ optionName: 'limit',
42
+ min: 1,
43
+ max: 100,
44
+ defaultValue: 25,
45
+ }, fail),
46
+ merchOnly: true,
47
+ });
48
+ };
49
+ const runKeywordHistory = async (args, client, options, fail) => {
50
+ const keyword = requireKeyword(args, fail, 'keyword history');
51
+ return await client.keyword.history.query({
52
+ keyword,
53
+ marketplaceId: KEYWORD_MARKETPLACE_ID,
54
+ rangeDays: parseIntegerOption({
55
+ value: options.rangeDays ?? options.days,
56
+ optionName: 'rangeDays',
57
+ min: 7,
58
+ max: 365,
59
+ defaultValue: 90,
60
+ }, fail),
61
+ });
62
+ };
63
+ const requireKeyword = (args, fail, commandName) => {
64
+ const keyword = args.join(' ').trim();
65
+ if (!keyword) {
66
+ fail('INVALID_INPUT', `${commandName} requires text`);
67
+ }
68
+ return keyword;
69
+ };
@@ -1,22 +1,26 @@
1
1
  import type { CliConfig } from './cli-config';
2
- import { type HistoryMetricAlias } from './history-response';
2
+ import { type HistoryBucket, type HistoryMetricAlias } from './history-options';
3
3
  type CliFail = (code: string, message: string, details?: unknown) => never;
4
- export type CliOptionValues = {
4
+ export interface CliOptionValues {
5
5
  baseUrl?: string;
6
6
  marketplace?: string;
7
- asin?: string[];
8
7
  metrics?: string;
9
8
  startAt?: string;
10
9
  endAt?: string;
11
10
  days?: string;
11
+ rangeDays?: string;
12
12
  limit?: string;
13
+ bucket?: string;
14
+ cursor?: string;
15
+ refresh?: boolean;
13
16
  help?: boolean;
14
17
  version?: boolean;
15
- };
18
+ }
16
19
  export declare const requireMarketplaceId: (values: CliOptionValues, config: CliConfig) => string;
17
- export declare const requireAsins: (commandArgs: string[], values: CliOptionValues, fail: CliFail) => string[];
18
- export declare const requireSingleAsin: (commandArgs: string[], values: CliOptionValues, fail: CliFail) => string;
20
+ export declare const requireSingleAsin: (commandArgs: string[], fail: CliFail, commandName?: string) => string;
21
+ export declare const requireAsins: (commandArgs: string[], fail: CliFail, commandName?: string) => string[];
19
22
  export declare const resolveHistoryMetrics: (values: CliOptionValues, fail: CliFail) => HistoryMetricAlias[];
23
+ export declare const resolveHistoryBucket: (values: CliOptionValues, fail: CliFail) => HistoryBucket;
20
24
  export declare const resolveHistoryWindow: (values: CliOptionValues, fail: CliFail) => {
21
25
  days: number;
22
26
  endAt?: string | undefined;
@@ -1,31 +1,34 @@
1
- import { HISTORY_METRIC_ALIASES } from './history-response.js';
1
+ import { HISTORY_BUCKETS, HISTORY_METRIC_ALIASES, } from './history-options.js';
2
2
  const ASIN_REGEX = /^[A-Z0-9]{10}$/i;
3
3
  const TRAILING_SLASHES_REGEX = /\/+$/;
4
4
  const API_SUFFIX_REGEX = /\/api$/i;
5
5
  const DEFAULT_MARKETPLACE_ID = 'ATVPDKIKX0DER';
6
6
  export const requireMarketplaceId = (values, config) => {
7
- return values.marketplace ?? config.marketplaceId ?? process.env.RR_MARKETPLACE_ID ?? DEFAULT_MARKETPLACE_ID;
7
+ return (values.marketplace ??
8
+ config.marketplaceId ??
9
+ process.env.RR_MARKETPLACE_ID ??
10
+ DEFAULT_MARKETPLACE_ID);
8
11
  };
9
- export const requireAsins = (commandArgs, values, fail) => {
10
- const candidates = [...commandArgs, ...(values.asin ?? []), ...resolveEnvAsins()]
11
- .map(value => value.trim())
12
- .filter(Boolean);
13
- if (candidates.length === 0) {
14
- fail('INVALID_INPUT', 'at least one asin is required');
12
+ export const requireSingleAsin = (commandArgs, fail, commandName = 'product history') => {
13
+ if (commandArgs.length !== 1) {
14
+ fail('INVALID_INPUT', `${commandName} requires exactly one ASIN`);
15
15
  }
16
- return Array.from(new Set(candidates.map(value => normalizeAsin(value, fail))));
16
+ return normalizeAsin(commandArgs[0] ?? '', fail);
17
17
  };
18
- export const requireSingleAsin = (commandArgs, values, fail) => {
19
- const asins = requireAsins(commandArgs, values, fail);
20
- if (asins.length !== 1) {
21
- fail('INVALID_INPUT', 'products history requires exactly one asin');
18
+ export const requireAsins = (commandArgs, fail, commandName = 'product get') => {
19
+ if (commandArgs.length < 1 || commandArgs.length > 200) {
20
+ fail('INVALID_INPUT', `${commandName} requires between 1 and 200 ASINs`);
21
+ }
22
+ const asins = commandArgs.map(value => normalizeAsin(value, fail));
23
+ if (new Set(asins).size !== asins.length) {
24
+ fail('INVALID_INPUT', `${commandName} requires unique ASINs`);
22
25
  }
23
- return asins[0];
26
+ return asins;
24
27
  };
25
28
  export const resolveHistoryMetrics = (values, fail) => {
26
- const requested = (values.metrics ?? process.env.RR_HISTORY_METRICS ?? 'bsr,price')
29
+ const requested = (values.metrics ?? process.env.RR_HISTORY_METRICS ?? 'salesRank,price')
27
30
  .split(',')
28
- .map(value => value.trim().toLowerCase())
31
+ .map(value => normalizeHistoryMetric(value.trim()))
29
32
  .filter(Boolean);
30
33
  if (requested.length === 0) {
31
34
  fail('INVALID_INPUT', 'metrics cannot be empty');
@@ -38,8 +41,19 @@ export const resolveHistoryMetrics = (values, fail) => {
38
41
  }
39
42
  return Array.from(new Set(requested));
40
43
  };
44
+ export const resolveHistoryBucket = (values, fail) => {
45
+ const requested = (values.bucket ?? 'auto').trim().toLowerCase();
46
+ if (!HISTORY_BUCKETS.includes(requested)) {
47
+ fail('INVALID_INPUT', `unsupported history bucket: ${requested}`, {
48
+ supportedBuckets: HISTORY_BUCKETS,
49
+ });
50
+ }
51
+ return requested;
52
+ };
41
53
  export const resolveHistoryWindow = (values, fail) => {
42
- const startAt = values.startAt ? normalizeDateOption('startAt', values.startAt, fail) : undefined;
54
+ const startAt = values.startAt
55
+ ? normalizeDateOption('startAt', values.startAt, fail)
56
+ : undefined;
43
57
  const endAt = values.endAt ? normalizeDateOption('endAt', values.endAt, fail) : undefined;
44
58
  if (values.days && (startAt || endAt)) {
45
59
  fail('INVALID_INPUT', 'use --days or --startAt/--endAt, not both');
@@ -60,7 +74,10 @@ export const resolveBaseUrl = (values, config, defaultBaseUrl, fail) => {
60
74
  return normalizeBaseUrl(values.baseUrl ?? config.baseUrl ?? process.env.RR_API_URL ?? defaultBaseUrl, fail);
61
75
  };
62
76
  export const normalizeBaseUrl = (value, fail) => {
63
- const normalized = value.trim().replace(TRAILING_SLASHES_REGEX, '').replace(API_SUFFIX_REGEX, '');
77
+ const normalized = value
78
+ .trim()
79
+ .replace(TRAILING_SLASHES_REGEX, '')
80
+ .replace(API_SUFFIX_REGEX, '');
64
81
  if (!normalized) {
65
82
  fail('INVALID_INPUT', 'base url cannot be empty');
66
83
  }
@@ -90,9 +107,12 @@ const normalizeAsin = (value, fail) => {
90
107
  }
91
108
  return normalized;
92
109
  };
93
- const resolveEnvAsins = () => {
94
- return [
95
- ...(process.env.RR_ASINS ? process.env.RR_ASINS.split(',') : []),
96
- ...(process.env.RR_ASIN ? [process.env.RR_ASIN] : []),
97
- ];
110
+ const normalizeHistoryMetric = (value) => {
111
+ if (value.toLowerCase() === 'salesrank') {
112
+ return 'salesRank';
113
+ }
114
+ if (value.toLowerCase() === 'price') {
115
+ return 'price';
116
+ }
117
+ return value;
98
118
  };
@@ -1,4 +1,4 @@
1
- import { CliAuthError } from './cli-auth.js';
1
+ import { CliAuthError } from './cli-auth-error.js';
2
2
  import { CliStorageError } from './cli-config.js';
3
3
  const OUTPUT_PRETTY = true;
4
4
  export const printSuccess = (data) => {
@@ -0,0 +1,10 @@
1
+ import type { createRankWranglerClient } from '@rankwrangler/http-client';
2
+ import type { CliConfig } from './cli-config';
3
+ import { type CliOptionValues } from './cli-options';
4
+ type CliFail = (code: string, message: string, details?: unknown) => never;
5
+ type CliClient = ReturnType<typeof createRankWranglerClient>;
6
+ export declare const runProductCommand: (command: {
7
+ verb: string;
8
+ args: string[];
9
+ }, client: CliClient, config: CliConfig, options: CliOptionValues, fail: CliFail) => Promise<unknown>;
10
+ export {};
@@ -0,0 +1,62 @@
1
+ import { parseIntegerOption, requireAsins, requireMarketplaceId, requireSingleAsin, resolveHistoryBucket, resolveHistoryMetrics, resolveHistoryWindow, } from './cli-options.js';
2
+ export const runProductCommand = async (command, client, config, options, fail) => {
3
+ if (command.verb !== 'search' && options.refresh) {
4
+ fail('INVALID_INPUT', '--refresh is only supported for product search');
5
+ }
6
+ if (command.verb === 'get') {
7
+ return await runProductGet(command.args, client, config, options, fail);
8
+ }
9
+ if (command.verb === 'search') {
10
+ return await runProductSearch(command.args, client, options, fail);
11
+ }
12
+ if (command.verb === 'history') {
13
+ return await runProductHistory(command.args, client, config, options, fail);
14
+ }
15
+ fail('UNKNOWN_COMMAND', 'Unknown Product command', {
16
+ command: `product ${command.verb}`,
17
+ });
18
+ };
19
+ const runProductGet = async (args, client, config, options, fail) => {
20
+ const marketplaceId = requireMarketplaceId(options, config);
21
+ const asins = requireAsins(args, fail);
22
+ if (asins.length === 1) {
23
+ return await client.product.get.mutate({
24
+ marketplaceId,
25
+ asin: asins[0],
26
+ });
27
+ }
28
+ return await client.product.getMany.mutate({
29
+ products: asins.map(asin => ({ marketplaceId, asin })),
30
+ });
31
+ };
32
+ const runProductSearch = async (args, client, options, fail) => {
33
+ const term = args.join(' ').trim();
34
+ if (!term) {
35
+ fail('INVALID_INPUT', 'product search requires a keyword');
36
+ }
37
+ return await client.product.search.mutate({
38
+ term,
39
+ refresh: Boolean(options.refresh),
40
+ });
41
+ };
42
+ const runProductHistory = async (args, client, config, options, fail) => {
43
+ const asin = requireSingleAsin(args, fail, 'product history');
44
+ const historyOptions = resolveHistoryOptions(options, fail);
45
+ return await client.product.history.mutate({
46
+ marketplaceId: requireMarketplaceId(options, config),
47
+ asin,
48
+ ...historyOptions,
49
+ });
50
+ };
51
+ const resolveHistoryOptions = (options, fail) => ({
52
+ metrics: resolveHistoryMetrics(options, fail),
53
+ bucket: resolveHistoryBucket(options, fail),
54
+ limit: parseIntegerOption({
55
+ value: options.limit,
56
+ optionName: 'limit',
57
+ min: 1,
58
+ max: 10_000,
59
+ defaultValue: 5000,
60
+ }, fail),
61
+ ...resolveHistoryWindow(options, fail),
62
+ });
@@ -0,0 +1,4 @@
1
+ export declare const HISTORY_METRIC_ALIASES: readonly ["salesRank", "price"];
2
+ export declare const HISTORY_BUCKETS: readonly ["auto", "day", "week", "month"];
3
+ export type HistoryMetricAlias = (typeof HISTORY_METRIC_ALIASES)[number];
4
+ export type HistoryBucket = (typeof HISTORY_BUCKETS)[number];
@@ -0,0 +1,2 @@
1
+ export const HISTORY_METRIC_ALIASES = ['salesRank', 'price'];
2
+ export const HISTORY_BUCKETS = ['auto', 'day', 'week', 'month'];
package/dist/index.js CHANGED
@@ -2,23 +2,28 @@
2
2
  import { parseArgs } from 'node:util';
3
3
  import { createRankWranglerClient, DEFAULT_API_BASE_URL } from '@rankwrangler/http-client';
4
4
  import { resolveApiKey, runAuthCommand } from './cli-auth.js';
5
- import { printBundledChangelog, printCliVersion } from './cli-metadata.js';
6
- import { runConfigCommand } from './cli-config-command.js';
7
5
  import { loadCliContext, loadCliPathsOrDefault } from './cli-config.js';
8
- import { parseIntegerOption, requireAsins, requireMarketplaceId, requireSingleAsin, resolveBaseUrl, resolveHistoryMetrics, resolveHistoryWindow, } from './cli-options.js';
6
+ import { runConfigCommand } from './cli-config-command.js';
7
+ import { runKeywordCommand } from './cli-keyword-command.js';
8
+ import { printBundledChangelog, printCliVersion } from './cli-metadata.js';
9
+ import { resolveBaseUrl } from './cli-options.js';
9
10
  import { fail, printSuccess, resolveError } from './cli-output.js';
10
- import { buildCliHistoryResponse } from './history-response.js';
11
+ import { runProductCommand } from './cli-product-command.js';
11
12
  import { printUsage } from './usage.js';
12
13
  const SUPPORTED_COMMANDS = new Set([
13
- 'products:get',
14
- 'products:history',
15
- 'license:status',
16
- 'license:validate',
14
+ 'product:get',
15
+ 'product:search',
16
+ 'product:history',
17
+ 'keyword:get',
18
+ 'keyword:search',
19
+ 'keyword:history',
17
20
  'auth:status',
18
21
  'auth:set',
19
22
  'auth:clear',
20
23
  'config:show',
21
- 'config:clear',
24
+ 'config:get',
25
+ 'config:unset',
26
+ 'config:reset',
22
27
  'config:set',
23
28
  ]);
24
29
  const { positionals, values } = parseArgs({
@@ -28,12 +33,16 @@ const { positionals, values } = parseArgs({
28
33
  version: { type: 'boolean' },
29
34
  baseUrl: { type: 'string' },
30
35
  marketplace: { type: 'string', short: 'm' },
31
- asin: { type: 'string', multiple: true },
32
36
  metrics: { type: 'string' },
33
37
  startAt: { type: 'string' },
34
38
  endAt: { type: 'string' },
35
39
  days: { type: 'string' },
40
+ rangeDays: { type: 'string' },
36
41
  limit: { type: 'string' },
42
+ bucket: { type: 'string' },
43
+ cursor: { type: 'string' },
44
+ refresh: { type: 'boolean' },
45
+ stdin: { type: 'boolean' },
37
46
  },
38
47
  allowPositionals: true,
39
48
  });
@@ -48,11 +57,9 @@ const main = async () => {
48
57
  process.exit(values.help ? 0 : 1);
49
58
  }
50
59
  const metaCommand = resolveMetaCommand(positionals);
51
- if (metaCommand) {
52
- if (metaCommand.name === 'changelog') {
53
- printBundledChangelog();
54
- return;
55
- }
60
+ if (metaCommand?.name === 'changelog') {
61
+ printBundledChangelog();
62
+ return;
56
63
  }
57
64
  const command = resolveCommandOrFail(positionals);
58
65
  if (!isSupportedCommand(command)) {
@@ -66,70 +73,32 @@ const main = async () => {
66
73
  return;
67
74
  }
68
75
  if (command.resource === 'auth') {
69
- printSuccess(await runAuthCommand(command, fail));
76
+ printSuccess(await runAuthCommand(command, fail, {
77
+ stdin: Boolean(values.stdin),
78
+ }));
70
79
  return;
71
80
  }
72
81
  const apiKey = await resolveApiKey();
73
82
  if (!apiKey) {
74
- fail('MISSING_CONFIG', 'license key is required. run `rw auth set <licenseKey>` or set RR_LICENSE_KEY');
83
+ fail('MISSING_CONFIG', 'Merchbase API key is required. run `rw auth set`, `rw auth set --stdin`, or set MERCHBASE_API_KEY');
75
84
  }
76
85
  const client = createRankWranglerClient({
77
86
  baseUrl: resolveBaseUrl(optionValues, config, DEFAULT_API_BASE_URL, fail),
78
87
  apiKey,
79
88
  });
80
- printSuccess(await runApiCommand(command, client, config));
89
+ printSuccess(await runApiCommand(command, client, config, optionValues));
81
90
  };
82
- const runApiCommand = async (command, client, config) => {
83
- if (command.resource === 'products' && command.verb === 'get') {
84
- const optionValues = values;
85
- const marketplaceId = requireMarketplaceId(optionValues, config);
86
- const asins = requireAsins(command.args, optionValues, fail);
87
- if (asins.length === 1) {
88
- return client.getProductInfo.mutate({ marketplaceId, asin: asins[0] });
89
- }
90
- return client.getProductInfoBatch.mutate({ marketplaceId, asins });
91
- }
92
- if (command.resource === 'products' && command.verb === 'history') {
93
- return runProductHistoryCommand(command, client, config);
91
+ const runApiCommand = async (command, client, config, options) => {
92
+ if (command.resource === 'product') {
93
+ return await runProductCommand(command, client, config, options, fail);
94
94
  }
95
- if (command.resource === 'license' && command.verb === 'status') {
96
- return client.license.status.mutate();
97
- }
98
- if (command.resource === 'license' && command.verb === 'validate') {
99
- return client.license.validate.mutate();
95
+ if (command.resource === 'keyword') {
96
+ return await runKeywordCommand(command, client, options, fail);
100
97
  }
101
98
  fail('UNKNOWN_COMMAND', 'Unknown command', {
102
99
  command: `${command.resource} ${command.verb}`,
103
100
  });
104
101
  };
105
- const runProductHistoryCommand = async (command, client, config) => {
106
- const optionValues = values;
107
- const asin = requireSingleAsin(command.args, optionValues, fail);
108
- const marketplaceId = requireMarketplaceId(optionValues, config);
109
- const metrics = resolveHistoryMetrics(optionValues, fail);
110
- const historyWindow = resolveHistoryWindow(optionValues, fail);
111
- const limit = parseIntegerOption({
112
- value: optionValues.limit,
113
- optionName: 'limit',
114
- min: 1,
115
- max: 10000,
116
- defaultValue: 5000,
117
- }, fail);
118
- const response = await client.getProductHistory.mutate({
119
- marketplaceId,
120
- asin,
121
- metrics,
122
- format: 'agent',
123
- limit,
124
- ...historyWindow,
125
- });
126
- return buildCliHistoryResponse({
127
- asin,
128
- marketplaceId,
129
- metrics,
130
- response: response,
131
- });
132
- };
133
102
  const resolveMetaCommand = (inputPositionals) => {
134
103
  if (inputPositionals.length === 1 && inputPositionals[0] === 'changelog') {
135
104
  return { name: 'changelog' };
@@ -138,7 +107,7 @@ const resolveMetaCommand = (inputPositionals) => {
138
107
  };
139
108
  const resolveCommand = (inputPositionals) => {
140
109
  const [first, second, ...rest] = inputPositionals;
141
- if (!first || !second) {
110
+ if (!(first && second)) {
142
111
  return null;
143
112
  }
144
113
  return {
@@ -155,9 +124,7 @@ const resolveCommandOrFail = (inputPositionals) => {
155
124
  fail('UNKNOWN_COMMAND', 'Unknown command', { command: inputPositionals.join(' ') });
156
125
  throw new Error('unreachable');
157
126
  };
158
- const isSupportedCommand = (command) => {
159
- return SUPPORTED_COMMANDS.has(`${command.resource}:${command.verb}`);
160
- };
127
+ const isSupportedCommand = (command) => SUPPORTED_COMMANDS.has(`${command.resource}:${command.verb}`);
161
128
  await main().catch(error => {
162
129
  const resolved = resolveError(error);
163
130
  fail(resolved.code, resolved.message, resolved.details);
package/dist/usage.js CHANGED
@@ -1,4 +1,4 @@
1
- import { LICENSE_KEY_ENV_VAR } from './cli-auth.js';
1
+ import { API_KEY_ENV_VAR } from './cli-auth.js';
2
2
  const DEFAULT_MARKETPLACE_ID = 'ATVPDKIKX0DER';
3
3
  export const printUsage = (paths) => {
4
4
  const usage = [
@@ -15,54 +15,65 @@ export const printUsage = (paths) => {
15
15
  '',
16
16
  'COMMANDS',
17
17
  ' changelog',
18
- ' products get <ASIN...>',
19
- ' products history <ASIN>',
20
- ' license status',
21
- ' license validate',
18
+ ' product get <ASIN...>',
19
+ ' product search <keyword>',
20
+ ' product history <ASIN>',
21
+ ' keyword get <keyword>',
22
+ ' keyword search <text>',
23
+ ' keyword history <keyword>',
22
24
  ' auth status',
23
- ' auth set <licenseKey>',
25
+ ' auth set [apiKey]',
26
+ ' auth set --stdin',
24
27
  ' auth clear',
25
28
  ' config show',
26
- ' config clear',
29
+ ' config get <key>',
27
30
  ' config set base-url <origin>',
28
31
  ' config set marketplace <marketplaceId>',
29
32
  ' config set storage-dir <path>',
33
+ ' config unset <key>',
34
+ ' config reset',
30
35
  '',
31
36
  'OPTIONS',
32
37
  ' -h, --help Show this help message',
33
38
  ' --version Print the CLI version',
34
39
  ' --baseUrl <origin> Override API origin',
35
40
  ` -m, --marketplace <id> Override marketplace (default: ${DEFAULT_MARKETPLACE_ID})`,
36
- ' --asin <ASIN> Add ASIN (repeatable)',
37
- ' --metrics <list> History metrics: bsr,price (default: bsr,price)',
38
- ' --days <N> History lookback window (30-3650, default: 365)',
39
- ' --startAt <ISO> History range start (ISO date/time)',
40
- ' --endAt <ISO> History range end (ISO date/time)',
41
- ' --limit <N> Max points per metric (1-10000, default: 5000)',
41
+ ' --refresh Product search refresh (separate Search contract)',
42
+ ' --metrics <list> Product history metrics: salesRank,price (default: salesRank,price)',
43
+ ' --bucket <unit> Product history buckets: auto,day,week,month (default: auto)',
44
+ ' --days <N> Product history lookback (30-3650, default: 365)',
45
+ ' --rangeDays <N> Keyword history lookback (7-365, default: 90)',
46
+ ' --startAt <ISO> Product history range start (ISO date/time)',
47
+ ' --endAt <ISO> Product history range end (ISO date/time)',
48
+ ' --limit <N> Result limit',
49
+ ' --cursor <N> Keyword search offset (default: 0)',
50
+ ' --stdin Read auth secret from standard input',
42
51
  '',
43
52
  'FILES',
44
53
  ` ${paths.configPath} Active CLI config`,
45
54
  ` ${paths.globalConfigPath} Global CLI settings`,
46
55
  '',
47
56
  'ENVIRONMENT',
48
- ` ${LICENSE_KEY_ENV_VAR} License key override for CI/automation`,
57
+ ` ${API_KEY_ENV_VAR} Merchbase API key override for CI/automation`,
49
58
  ' RR_API_URL API origin fallback',
50
59
  ' RR_MARKETPLACE_ID Marketplace fallback',
51
60
  ' RR_STORAGE_DIR Storage directory override',
52
- ' RR_ASIN Single ASIN fallback',
53
- ' RR_ASINS Comma-separated ASIN fallback',
54
- ' RR_HISTORY_METRICS Comma-separated history metrics fallback',
61
+ ' RR_HISTORY_METRICS Comma-separated product history metrics fallback',
55
62
  '',
56
63
  'EXAMPLES',
57
- ' rw auth set rrk_...',
58
- ` ${LICENSE_KEY_ENV_VAR}=rrk_... rw products get B0DV53VS61`,
64
+ ' rw auth set ak_...',
65
+ ' printf %s "$MERCHBASE_API_KEY" | rw auth set --stdin',
66
+ ` ${API_KEY_ENV_VAR}=ak_... rw product get B0DV53VS61`,
67
+ ' rw product get B0DV53VS61 B012345678',
68
+ ' rw product search "retro gardening shirt"',
69
+ ' rw product history B0DV53VS61 --bucket week',
70
+ ' rw keyword get "retro gardening shirt"',
71
+ ' rw keyword search "retro gardening" --limit 10',
72
+ ' rw keyword history "retro gardening shirt" --rangeDays 90',
59
73
  ' RR_STORAGE_DIR=~/.config/rankwrangler rw config show',
60
74
  ' rw --version',
61
75
  ' rw changelog',
62
- ' rw config set storage-dir ~/.config/rankwrangler',
63
- ' rw products history B0DV53VS61 --metrics bsr,price',
64
- ' rw products history B0DV53VS61 --startAt 2025-01-01 --endAt 2025-12-31',
65
- ' rankwrangler license status',
76
+ ' rankwrangler auth status',
66
77
  ];
67
78
  console.log(usage.join('\n'));
68
79
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankwrangler/cli",
3
- "version": "0.5.0",
3
+ "version": "0.8.0",
4
4
  "description": "Official RankWrangler command line interface",
5
5
  "type": "module",
6
6
  "bin": {
@@ -16,19 +16,21 @@
16
16
  "scripts": {
17
17
  "build": "tsc -p tsconfig.json && node scripts/fix-dist-imports.mjs && node scripts/sync-changelog.mjs",
18
18
  "prepack": "node scripts/sync-changelog.mjs",
19
- "test:e2e": "bun test test/index.e2e.test.ts",
19
+ "test:e2e": "bun test test/*.e2e.test.ts",
20
20
  "prepublishOnly": "node ../../scripts/release/ensure-cli-http-client-published.mjs"
21
21
  },
22
22
  "publishConfig": {
23
- "access": "public"
23
+ "access": "public",
24
+ "provenance": true
24
25
  },
25
26
  "dependencies": {
26
- "@rankwrangler/http-client": "^0.5.0",
27
- "keytar": "^7.9.0"
27
+ "@merchbaseco/access": "0.3.0",
28
+ "@rankwrangler/http-client": "^0.8.0",
29
+ "keytar": "7.9.0"
28
30
  },
29
31
  "devDependencies": {
30
- "@types/node": "^24.3.0",
31
- "typescript": "^5.9.2"
32
+ "@types/node": "24.10.12",
33
+ "typescript": "5.9.3"
32
34
  },
33
35
  "packageManager": "bun@1.1.27"
34
36
  }