@rankwrangler/cli 0.5.0 → 0.6.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/README.md CHANGED
@@ -14,23 +14,30 @@ npm install -g @rankwrangler/cli
14
14
  rw --version
15
15
  rw changelog
16
16
  rw auth set rrk_...
17
+ printf %s "$RR_LICENSE_KEY" | rw auth set --stdin
17
18
  rw config set storage-dir ~/.config/rankwrangler
19
+ rw config get marketplace
20
+ rw config unset marketplace
18
21
  rw products get B0DV53VS61
19
- rw products history B0DV53VS61 --metrics bsr,price
22
+ rw products history B0DV53VS61 --metrics bsr,price --bucket week
20
23
  RR_LICENSE_KEY=rrk_... rw license status
21
24
  # `rankwrangler` is also supported as an alias
22
25
  ```
23
26
 
24
- `rw auth set <licenseKey>` stores the license key in the platform secure store
27
+ `rw auth set [licenseKey]` stores the license key in the platform secure store
25
28
  (`macOS Keychain` on macOS). `rw auth status` reports whether the CLI will use an env override,
26
- stored auth, or no auth. `rw auth clear` removes the stored key.
29
+ stored auth, or no auth. `rw auth set --stdin` reads the key from standard input for agents and
30
+ scripts. `rw auth clear` removes the stored key.
27
31
 
28
32
  `rw config set storage-dir <path>` saves the active storage directory globally. After that, every
29
33
  CLI command reads and writes its non-secret config/data from that directory, while preserving
30
- existing config values when switching. `RR_LICENSE_KEY`, `RR_STORAGE_DIR`, `RR_API_URL`, and
31
- `RR_MARKETPLACE_ID` override saved CLI state when set for CI, automation, or agent runtimes.
32
- `rw --version` prints the installed CLI version. `rw changelog` prints the latest bundled release
33
- notes shipped with the package.
34
+ existing config values when switching. `rw config get <key>`, `rw config unset <key>`, and
35
+ `rw config reset` inspect or remove non-secret config without touching stored auth.
36
+ `RR_LICENSE_KEY`, `RR_STORAGE_DIR`, `RR_API_URL`, and `RR_MARKETPLACE_ID` override saved CLI state
37
+ when set for CI, automation, or agent runtimes.
38
+ `rw products history` ensures product/history data before returning bucketed history (`auto`,
39
+ `day`, `week`, or `month`), not raw point series. `rw --version` prints the installed CLI version.
40
+ `rw changelog` prints the latest bundled release notes shipped with the package.
34
41
 
35
42
  ## Development
36
43
 
@@ -60,9 +67,9 @@ matching `@rankwrangler/http-client` version is already available on npm.
60
67
 
61
68
  ```bash
62
69
  cd packages/cli
63
- set -a
64
- source ../../.env
65
- set +a
66
- npm whoami --userconfig ../../.npmrc
67
- npm publish --access public --userconfig ../../.npmrc
70
+ node ../../scripts/release/with-npm-token.mjs npm whoami --userconfig ../../.npmrc
71
+ node ../../scripts/release/with-npm-token.mjs npm publish --access public --userconfig ../../.npmrc
68
72
  ```
73
+
74
+ On macOS, the helper reads `NPM_TOKEN` from the environment first and otherwise falls back to the
75
+ Keychain item `rankwrangler-npm-token` for account `$USER`.
package/dist/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v0.6.0 - 2026-06-15
6
+
7
+ ### Added
8
+
9
+ - Add `rw auth set --stdin` and hidden local auth entry so agents can pipe license keys and humans
10
+ can avoid shell history.
11
+ - Add `rw config get`, `rw config unset`, and `rw config reset` for explicit non-secret config
12
+ inspection and cleanup.
13
+
14
+ ### Changed
15
+
16
+ - Show auth source/status in `rw config show` without printing secrets.
17
+ - Update CLI help, README, CLI spec, and e2e coverage for the clearer auth/config surface.
18
+
19
+ ### Removed
20
+
21
+ - Remove `rw config clear` in favor of `rw config unset <key>` and `rw config reset`.
22
+
5
23
  ## v0.5.0 - 2026-03-18
6
24
 
7
25
  ### Added
@@ -6,13 +6,16 @@ type AuthCommand = {
6
6
  verb: string;
7
7
  args: string[];
8
8
  };
9
+ type AuthCommandOptions = {
10
+ stdin?: boolean;
11
+ };
9
12
  export declare class CliAuthError extends Error {
10
13
  readonly code: string;
11
14
  readonly details?: unknown;
12
15
  constructor(code: string, message: string, details?: unknown);
13
16
  }
14
17
  export declare const resolveApiKey: () => Promise<string | undefined>;
15
- export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Promise<{
18
+ export declare const runAuthCommand: (command: AuthCommand, fail: CliFail, options?: AuthCommandOptions) => Promise<{
16
19
  source: string;
17
20
  envOverride: boolean;
18
21
  secureStore: {
@@ -83,4 +86,27 @@ export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Pr
83
86
  error: string;
84
87
  };
85
88
  }>;
89
+ export declare const buildAuthStatus: () => Promise<{
90
+ source: string;
91
+ envOverride: boolean;
92
+ secureStore: {
93
+ backend: string;
94
+ serviceName: string;
95
+ accountName: string;
96
+ available: boolean;
97
+ hasStoredLicenseKey: boolean;
98
+ error?: undefined;
99
+ };
100
+ } | {
101
+ source: string;
102
+ envOverride: boolean;
103
+ secureStore: {
104
+ backend: string;
105
+ serviceName: string;
106
+ accountName: string;
107
+ available: boolean;
108
+ hasStoredLicenseKey: null;
109
+ error: string;
110
+ };
111
+ }>;
86
112
  export {};
package/dist/cli-auth.js CHANGED
@@ -1,5 +1,7 @@
1
+ import { spawnSync } from 'node:child_process';
1
2
  import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
2
3
  import path from 'node:path';
4
+ import readline from 'node:readline';
3
5
  export const LICENSE_KEY_ENV_VAR = 'RR_LICENSE_KEY';
4
6
  export const AUTH_SERVICE_NAME = 'RankWrangler CLI';
5
7
  export const AUTH_ACCOUNT_NAME = 'license-key';
@@ -22,12 +24,12 @@ export const resolveApiKey = async () => {
22
24
  const secureStore = await createSecureStore();
23
25
  return (await secureStore.get()) ?? undefined;
24
26
  };
25
- export const runAuthCommand = async (command, fail) => {
27
+ export const runAuthCommand = async (command, fail, options = {}) => {
26
28
  if (command.verb === 'status') {
27
29
  return buildAuthStatus();
28
30
  }
29
31
  if (command.verb === 'set') {
30
- const value = resolveAuthSetValue(command.args, fail);
32
+ const value = await resolveAuthSetValue(command.args, fail, options);
31
33
  const secureStore = await createSecureStore();
32
34
  await secureStore.set(value);
33
35
  return {
@@ -45,7 +47,7 @@ export const runAuthCommand = async (command, fail) => {
45
47
  }
46
48
  fail('UNKNOWN_COMMAND', 'Unknown auth command', { verb: command.verb });
47
49
  };
48
- const buildAuthStatus = async () => {
50
+ export const buildAuthStatus = async () => {
49
51
  const envLicenseKey = resolveEnvLicenseKey();
50
52
  try {
51
53
  const secureStore = await createSecureStore();
@@ -77,16 +79,65 @@ const buildAuthStatus = async () => {
77
79
  };
78
80
  }
79
81
  };
80
- const resolveAuthSetValue = (args, fail) => {
82
+ const resolveAuthSetValue = async (args, fail, options) => {
81
83
  const positionalValue = args.join(' ').trim();
82
84
  if (positionalValue) {
83
85
  return positionalValue;
84
86
  }
87
+ if (options.stdin) {
88
+ const stdinValue = await readSecretFromStdin();
89
+ if (stdinValue) {
90
+ return stdinValue;
91
+ }
92
+ fail('INVALID_INPUT', 'auth set --stdin received an empty license key');
93
+ }
85
94
  const envLicenseKey = resolveEnvLicenseKey();
86
95
  if (envLicenseKey) {
87
96
  return envLicenseKey;
88
97
  }
89
- fail('INVALID_INPUT', `auth set requires <licenseKey> or ${LICENSE_KEY_ENV_VAR}`);
98
+ if (process.stdin.isTTY && process.stderr.isTTY) {
99
+ const promptValue = await readSecretFromPrompt('RankWrangler license key: ');
100
+ if (promptValue) {
101
+ return promptValue;
102
+ }
103
+ fail('INVALID_INPUT', 'auth set received an empty license key');
104
+ }
105
+ fail('INVALID_INPUT', `auth set requires <licenseKey>, --stdin, or ${LICENSE_KEY_ENV_VAR}`);
106
+ };
107
+ const readSecretFromStdin = async () => {
108
+ const chunks = [];
109
+ for await (const chunk of process.stdin) {
110
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
111
+ }
112
+ return Buffer.concat(chunks).toString('utf8').trim();
113
+ };
114
+ const readSecretFromPrompt = async (prompt) => {
115
+ process.stderr.write(prompt);
116
+ setTerminalEcho(false);
117
+ const rl = readline.createInterface({
118
+ input: process.stdin,
119
+ output: process.stderr,
120
+ terminal: true,
121
+ });
122
+ try {
123
+ const value = await new Promise(resolve => {
124
+ rl.question('', resolve);
125
+ });
126
+ process.stderr.write('\n');
127
+ return value.trim();
128
+ }
129
+ finally {
130
+ rl.close();
131
+ setTerminalEcho(true);
132
+ }
133
+ };
134
+ const setTerminalEcho = (enabled) => {
135
+ if (process.platform === 'win32' || !process.stdin.isTTY) {
136
+ return;
137
+ }
138
+ spawnSync('stty', [enabled ? 'echo' : '-echo'], {
139
+ stdio: ['inherit', 'ignore', 'ignore'],
140
+ });
90
141
  };
91
142
  const resolveEnvLicenseKey = () => {
92
143
  const value = process.env[LICENSE_KEY_ENV_VAR]?.trim();
@@ -9,11 +9,93 @@ export declare const runConfigCommand: (command: ConfigCommand, config: CliConfi
9
9
  path: string;
10
10
  globalPath: string;
11
11
  config: CliConfig;
12
+ auth: {
13
+ source: string;
14
+ envOverride: boolean;
15
+ secureStore: {
16
+ backend: string;
17
+ serviceName: string;
18
+ accountName: string;
19
+ available: boolean;
20
+ hasStoredLicenseKey: boolean;
21
+ error?: undefined;
22
+ };
23
+ } | {
24
+ source: string;
25
+ envOverride: boolean;
26
+ secureStore: {
27
+ backend: string;
28
+ serviceName: string;
29
+ accountName: string;
30
+ available: boolean;
31
+ hasStoredLicenseKey: null;
32
+ error: string;
33
+ };
34
+ };
12
35
  } | {
13
- cleared: boolean;
36
+ key: "base-url" | "marketplace" | "storage-dir";
37
+ value: string | null;
38
+ } | {
39
+ unset: "base-url" | "marketplace" | "storage-dir";
40
+ storageDir: string;
41
+ path: string;
42
+ globalPath: string;
43
+ config: CliConfig;
44
+ auth: {
45
+ source: string;
46
+ envOverride: boolean;
47
+ secureStore: {
48
+ backend: string;
49
+ serviceName: string;
50
+ accountName: string;
51
+ available: boolean;
52
+ hasStoredLicenseKey: boolean;
53
+ error?: undefined;
54
+ };
55
+ } | {
56
+ source: string;
57
+ envOverride: boolean;
58
+ secureStore: {
59
+ backend: string;
60
+ serviceName: string;
61
+ accountName: string;
62
+ available: boolean;
63
+ hasStoredLicenseKey: null;
64
+ error: string;
65
+ };
66
+ };
67
+ key?: undefined;
68
+ value?: undefined;
69
+ } | {
70
+ reset: boolean;
14
71
  storageDir: string;
15
72
  path: string;
16
73
  globalPath: string;
17
74
  config: CliConfig;
75
+ auth: {
76
+ source: string;
77
+ envOverride: boolean;
78
+ secureStore: {
79
+ backend: string;
80
+ serviceName: string;
81
+ accountName: string;
82
+ available: boolean;
83
+ hasStoredLicenseKey: boolean;
84
+ error?: undefined;
85
+ };
86
+ } | {
87
+ source: string;
88
+ envOverride: boolean;
89
+ secureStore: {
90
+ backend: string;
91
+ serviceName: string;
92
+ accountName: string;
93
+ available: boolean;
94
+ hasStoredLicenseKey: null;
95
+ error: string;
96
+ };
97
+ };
98
+ key?: undefined;
99
+ value?: undefined;
18
100
  }>;
19
101
  export {};
@@ -1,14 +1,31 @@
1
- import { clearConfig, saveConfig, switchStorageDir } from './cli-config.js';
1
+ import { buildAuthStatus } from './cli-auth.js';
2
+ import { resetConfig, saveConfig, switchStorageDir, unsetStorageDir, } from './cli-config.js';
2
3
  import { normalizeBaseUrl } from './cli-options.js';
4
+ const CONFIG_KEYS = ['base-url', 'marketplace', 'storage-dir'];
3
5
  export const runConfigCommand = async (command, config, paths, fail) => {
4
6
  if (command.verb === 'show') {
5
7
  return buildConfigResponse(paths, config);
6
8
  }
7
- if (command.verb === 'clear') {
8
- await clearConfig(paths);
9
+ if (command.verb === 'get') {
10
+ const key = requireConfigKey(command.args[0], 'config get', fail);
9
11
  return {
10
- ...buildConfigResponse(paths, {}),
11
- cleared: true,
12
+ key,
13
+ value: getConfigValue(key, paths, config),
14
+ };
15
+ }
16
+ if (command.verb === 'unset') {
17
+ const key = requireConfigKey(command.args[0], 'config unset', fail);
18
+ const nextState = await unsetConfigValue(key, paths, config);
19
+ return {
20
+ ...(await buildConfigResponse(nextState.paths, nextState.config)),
21
+ unset: key,
22
+ };
23
+ }
24
+ if (command.verb === 'reset') {
25
+ const nextState = await resetConfig(paths);
26
+ return {
27
+ ...(await buildConfigResponse(nextState.paths, nextState.config)),
28
+ reset: true,
12
29
  };
13
30
  }
14
31
  if (command.verb !== 'set') {
@@ -38,19 +55,64 @@ export const runConfigCommand = async (command, config, paths, fail) => {
38
55
  nextConfig.marketplaceId = value;
39
56
  }
40
57
  else {
41
- fail('INVALID_INPUT', 'unsupported config key', {
42
- key,
43
- supportedKeys: ['base-url', 'marketplace', 'storage-dir'],
44
- });
58
+ failUnsupportedConfigKey(key, fail);
45
59
  }
46
60
  await saveConfig(paths, nextConfig);
47
61
  return buildConfigResponse(paths, nextConfig);
48
62
  };
49
- const buildConfigResponse = (paths, config) => {
63
+ const buildConfigResponse = async (paths, config) => {
50
64
  return {
51
65
  storageDir: paths.storageDir,
52
66
  path: paths.configPath,
53
67
  globalPath: paths.globalConfigPath,
54
68
  config,
69
+ auth: await buildAuthStatus(),
70
+ };
71
+ };
72
+ const requireConfigKey = (key, commandName, fail) => {
73
+ if (!key) {
74
+ fail('INVALID_INPUT', `${commandName} requires <key>`, {
75
+ supportedKeys: CONFIG_KEYS,
76
+ });
77
+ }
78
+ if (isConfigKey(key)) {
79
+ return key;
80
+ }
81
+ return failUnsupportedConfigKey(key, fail);
82
+ };
83
+ const isConfigKey = (key) => {
84
+ return CONFIG_KEYS.includes(key);
85
+ };
86
+ const getConfigValue = (key, paths, config) => {
87
+ switch (key) {
88
+ case 'base-url':
89
+ return config.baseUrl ?? null;
90
+ case 'marketplace':
91
+ return config.marketplaceId ?? null;
92
+ case 'storage-dir':
93
+ return paths.storageDir;
94
+ }
95
+ };
96
+ const unsetConfigValue = async (key, paths, config) => {
97
+ if (key === 'storage-dir') {
98
+ return unsetStorageDir(paths);
99
+ }
100
+ const nextConfig = { ...config };
101
+ if (key === 'base-url') {
102
+ nextConfig.baseUrl = undefined;
103
+ }
104
+ else {
105
+ nextConfig.marketplaceId = undefined;
106
+ }
107
+ await saveConfig(paths, nextConfig);
108
+ return {
109
+ paths,
110
+ config: nextConfig,
55
111
  };
56
112
  };
113
+ const failUnsupportedConfigKey = (key, fail) => {
114
+ fail('INVALID_INPUT', 'unsupported config key', {
115
+ key,
116
+ supportedKeys: CONFIG_KEYS,
117
+ });
118
+ };
@@ -19,8 +19,15 @@ export declare const loadCliContext: () => Promise<{
19
19
  config: CliConfig;
20
20
  }>;
21
21
  export declare const loadCliPathsOrDefault: () => Promise<CliPaths>;
22
- export declare const clearConfig: (paths: CliPaths) => Promise<void>;
22
+ export declare const resetConfig: (paths: CliPaths) => Promise<{
23
+ paths: CliPaths;
24
+ config: {};
25
+ }>;
23
26
  export declare const saveConfig: (paths: CliPaths, config: CliConfig) => Promise<void>;
27
+ export declare const unsetStorageDir: (paths: CliPaths) => Promise<{
28
+ paths: CliPaths;
29
+ config: CliConfig;
30
+ }>;
24
31
  export declare const switchStorageDir: ({ currentConfig, currentPaths, requestedStorageDir, }: {
25
32
  currentConfig: CliConfig;
26
33
  currentPaths: CliPaths;
@@ -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);
@@ -1,5 +1,5 @@
1
1
  import type { CliConfig } from './cli-config';
2
- import { type HistoryMetricAlias } from './history-response';
2
+ import { type HistoryBucket, type HistoryMetricAlias } from './history-response';
3
3
  type CliFail = (code: string, message: string, details?: unknown) => never;
4
4
  export type CliOptionValues = {
5
5
  baseUrl?: string;
@@ -10,6 +10,7 @@ export type CliOptionValues = {
10
10
  endAt?: string;
11
11
  days?: string;
12
12
  limit?: string;
13
+ bucket?: string;
13
14
  help?: boolean;
14
15
  version?: boolean;
15
16
  };
@@ -17,6 +18,7 @@ export declare const requireMarketplaceId: (values: CliOptionValues, config: Cli
17
18
  export declare const requireAsins: (commandArgs: string[], values: CliOptionValues, fail: CliFail) => string[];
18
19
  export declare const requireSingleAsin: (commandArgs: string[], values: CliOptionValues, fail: CliFail) => string;
19
20
  export declare const resolveHistoryMetrics: (values: CliOptionValues, fail: CliFail) => HistoryMetricAlias[];
21
+ export declare const resolveHistoryBucket: (values: CliOptionValues, fail: CliFail) => HistoryBucket;
20
22
  export declare const resolveHistoryWindow: (values: CliOptionValues, fail: CliFail) => {
21
23
  days: number;
22
24
  endAt?: string | undefined;
@@ -1,4 +1,4 @@
1
- import { HISTORY_METRIC_ALIASES } from './history-response.js';
1
+ import { HISTORY_BUCKETS, HISTORY_METRIC_ALIASES, } from './history-response.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;
@@ -38,6 +38,15 @@ export const resolveHistoryMetrics = (values, fail) => {
38
38
  }
39
39
  return Array.from(new Set(requested));
40
40
  };
41
+ export const resolveHistoryBucket = (values, fail) => {
42
+ const requested = (values.bucket ?? 'auto').trim().toLowerCase();
43
+ if (!HISTORY_BUCKETS.includes(requested)) {
44
+ fail('INVALID_INPUT', `unsupported history bucket: ${requested}`, {
45
+ supportedBuckets: HISTORY_BUCKETS,
46
+ });
47
+ }
48
+ return requested;
49
+ };
41
50
  export const resolveHistoryWindow = (values, fail) => {
42
51
  const startAt = values.startAt ? normalizeDateOption('startAt', values.startAt, fail) : undefined;
43
52
  const endAt = values.endAt ? normalizeDateOption('endAt', values.endAt, fail) : undefined;
@@ -1,6 +1,17 @@
1
1
  export declare const HISTORY_METRIC_ALIASES: readonly ["bsr", "price"];
2
+ export declare const HISTORY_BUCKETS: readonly ["auto", "day", "week", "month"];
2
3
  export type HistoryMetricAlias = (typeof HISTORY_METRIC_ALIASES)[number];
3
- type HistoryPointTuple = [string, number | null] | [string, null, 1];
4
+ export type HistoryBucket = (typeof HISTORY_BUCKETS)[number];
5
+ type HistoryBucketTuple = [string, number | null];
6
+ type HistoryBucketSummary = {
7
+ first: number | null;
8
+ latest: number | null;
9
+ min: number | null;
10
+ max: number | null;
11
+ count: number;
12
+ firstBucketAt: string | null;
13
+ latestBucketAt: string | null;
14
+ };
4
15
  export type AgentHistorySeries = {
5
16
  bsr?: {
6
17
  unit: 'rank';
@@ -8,13 +19,15 @@ export type AgentHistorySeries = {
8
19
  id: number;
9
20
  name: string | null;
10
21
  } | null;
11
- points: HistoryPointTuple[];
22
+ buckets: HistoryBucketTuple[];
23
+ summary: HistoryBucketSummary;
12
24
  };
13
25
  price?: {
14
26
  unit: 'minorCurrency';
15
27
  currencyCode: 'USD';
16
28
  valueScale: 100;
17
- points: HistoryPointTuple[];
29
+ buckets: HistoryBucketTuple[];
30
+ summary: HistoryBucketSummary;
18
31
  };
19
32
  };
20
33
  export type AgentHistoryResponse = {
@@ -22,6 +35,11 @@ export type AgentHistoryResponse = {
22
35
  status?: string;
23
36
  syncTriggered: boolean;
24
37
  latestImportAt: string | null;
38
+ range?: {
39
+ startAt: string;
40
+ endAt: string;
41
+ bucket: Exclude<HistoryBucket, 'auto'>;
42
+ };
25
43
  series?: AgentHistorySeries;
26
44
  };
27
45
  export declare const buildCliHistoryResponse: ({ asin, marketplaceId, metrics, response, }: {
@@ -30,12 +48,17 @@ export declare const buildCliHistoryResponse: ({ asin, marketplaceId, metrics, r
30
48
  metrics: HistoryMetricAlias[];
31
49
  response: AgentHistoryResponse;
32
50
  }) => {
51
+ series: AgentHistorySeries;
52
+ range?: {
53
+ startAt: string;
54
+ endAt: string;
55
+ bucket: Exclude<HistoryBucket, "auto">;
56
+ } | undefined;
33
57
  schemaVersion: number;
34
58
  asin: string;
35
59
  marketplaceId: string;
36
60
  status: string;
37
61
  latestImportAt: string | null;
38
62
  syncTriggered: boolean;
39
- series: AgentHistorySeries;
40
63
  };
41
64
  export {};
@@ -1,4 +1,5 @@
1
1
  export const HISTORY_METRIC_ALIASES = ['bsr', 'price'];
2
+ export const HISTORY_BUCKETS = ['auto', 'day', 'week', 'month'];
2
3
  export const buildCliHistoryResponse = ({ asin, marketplaceId, metrics, response, }) => {
3
4
  const series = {
4
5
  ...(metrics.includes('bsr') && response.series?.bsr ? { bsr: response.series.bsr } : {}),
@@ -6,15 +7,16 @@ export const buildCliHistoryResponse = ({ asin, marketplaceId, metrics, response
6
7
  ? { price: response.series.price }
7
8
  : {}),
8
9
  };
9
- const hasAnyPoints = Object.values(series).some(metricSeries => metricSeries.points.length > 0);
10
+ const hasAnyBuckets = Object.values(series).some(metricSeries => metricSeries.buckets.length > 0);
10
11
  const status = normalizeHistoryStatus(response.status);
11
12
  return {
12
- schemaVersion: response.schemaVersion ?? 1,
13
+ schemaVersion: response.schemaVersion ?? 2,
13
14
  asin,
14
15
  marketplaceId,
15
- status: status ?? (response.syncTriggered ? 'collecting' : hasAnyPoints ? 'ready' : 'empty'),
16
+ status: status ?? (response.syncTriggered ? 'collecting' : hasAnyBuckets ? 'ready' : 'empty'),
16
17
  latestImportAt: response.latestImportAt,
17
18
  syncTriggered: response.syncTriggered,
19
+ ...(response.range ? { range: response.range } : {}),
18
20
  series,
19
21
  };
20
22
  };
package/dist/index.js CHANGED
@@ -5,7 +5,7 @@ import { resolveApiKey, runAuthCommand } from './cli-auth.js';
5
5
  import { printBundledChangelog, printCliVersion } from './cli-metadata.js';
6
6
  import { runConfigCommand } from './cli-config-command.js';
7
7
  import { loadCliContext, loadCliPathsOrDefault } from './cli-config.js';
8
- import { parseIntegerOption, requireAsins, requireMarketplaceId, requireSingleAsin, resolveBaseUrl, resolveHistoryMetrics, resolveHistoryWindow, } from './cli-options.js';
8
+ import { parseIntegerOption, requireAsins, requireMarketplaceId, requireSingleAsin, resolveBaseUrl, resolveHistoryBucket, resolveHistoryMetrics, resolveHistoryWindow, } from './cli-options.js';
9
9
  import { fail, printSuccess, resolveError } from './cli-output.js';
10
10
  import { buildCliHistoryResponse } from './history-response.js';
11
11
  import { printUsage } from './usage.js';
@@ -18,7 +18,9 @@ const SUPPORTED_COMMANDS = new Set([
18
18
  'auth:set',
19
19
  'auth:clear',
20
20
  'config:show',
21
- 'config:clear',
21
+ 'config:get',
22
+ 'config:unset',
23
+ 'config:reset',
22
24
  'config:set',
23
25
  ]);
24
26
  const { positionals, values } = parseArgs({
@@ -34,6 +36,8 @@ const { positionals, values } = parseArgs({
34
36
  endAt: { type: 'string' },
35
37
  days: { type: 'string' },
36
38
  limit: { type: 'string' },
39
+ bucket: { type: 'string' },
40
+ stdin: { type: 'boolean' },
37
41
  },
38
42
  allowPositionals: true,
39
43
  });
@@ -66,12 +70,12 @@ const main = async () => {
66
70
  return;
67
71
  }
68
72
  if (command.resource === 'auth') {
69
- printSuccess(await runAuthCommand(command, fail));
73
+ printSuccess(await runAuthCommand(command, fail, { stdin: Boolean(values.stdin) }));
70
74
  return;
71
75
  }
72
76
  const apiKey = await resolveApiKey();
73
77
  if (!apiKey) {
74
- fail('MISSING_CONFIG', 'license key is required. run `rw auth set <licenseKey>` or set RR_LICENSE_KEY');
78
+ fail('MISSING_CONFIG', 'license key is required. run `rw auth set`, `rw auth set --stdin`, or set RR_LICENSE_KEY');
75
79
  }
76
80
  const client = createRankWranglerClient({
77
81
  baseUrl: resolveBaseUrl(optionValues, config, DEFAULT_API_BASE_URL, fail),
@@ -107,6 +111,7 @@ const runProductHistoryCommand = async (command, client, config) => {
107
111
  const asin = requireSingleAsin(command.args, optionValues, fail);
108
112
  const marketplaceId = requireMarketplaceId(optionValues, config);
109
113
  const metrics = resolveHistoryMetrics(optionValues, fail);
114
+ const bucket = resolveHistoryBucket(optionValues, fail);
110
115
  const historyWindow = resolveHistoryWindow(optionValues, fail);
111
116
  const limit = parseIntegerOption({
112
117
  value: optionValues.limit,
@@ -120,6 +125,7 @@ const runProductHistoryCommand = async (command, client, config) => {
120
125
  asin,
121
126
  metrics,
122
127
  format: 'agent',
128
+ bucket,
123
129
  limit,
124
130
  ...historyWindow,
125
131
  });
package/dist/usage.js CHANGED
@@ -20,13 +20,16 @@ export const printUsage = (paths) => {
20
20
  ' license status',
21
21
  ' license validate',
22
22
  ' auth status',
23
- ' auth set <licenseKey>',
23
+ ' auth set [licenseKey]',
24
+ ' auth set --stdin',
24
25
  ' auth clear',
25
26
  ' config show',
26
- ' config clear',
27
+ ' config get <key>',
27
28
  ' config set base-url <origin>',
28
29
  ' config set marketplace <marketplaceId>',
29
30
  ' config set storage-dir <path>',
31
+ ' config unset <key>',
32
+ ' config reset',
30
33
  '',
31
34
  'OPTIONS',
32
35
  ' -h, --help Show this help message',
@@ -35,10 +38,12 @@ export const printUsage = (paths) => {
35
38
  ` -m, --marketplace <id> Override marketplace (default: ${DEFAULT_MARKETPLACE_ID})`,
36
39
  ' --asin <ASIN> Add ASIN (repeatable)',
37
40
  ' --metrics <list> History metrics: bsr,price (default: bsr,price)',
41
+ ' --bucket <unit> History buckets: auto,day,week,month (default: auto)',
38
42
  ' --days <N> History lookback window (30-3650, default: 365)',
39
43
  ' --startAt <ISO> History range start (ISO date/time)',
40
44
  ' --endAt <ISO> History range end (ISO date/time)',
41
- ' --limit <N> Max points per metric (1-10000, default: 5000)',
45
+ ' --limit <N> Internal point cap (1-10000, default: 5000)',
46
+ ' --stdin Read auth secret from standard input',
42
47
  '',
43
48
  'FILES',
44
49
  ` ${paths.configPath} Active CLI config`,
@@ -55,13 +60,16 @@ export const printUsage = (paths) => {
55
60
  '',
56
61
  'EXAMPLES',
57
62
  ' rw auth set rrk_...',
63
+ ' printf %s "$RR_LICENSE_KEY" | rw auth set --stdin',
58
64
  ` ${LICENSE_KEY_ENV_VAR}=rrk_... rw products get B0DV53VS61`,
59
65
  ' RR_STORAGE_DIR=~/.config/rankwrangler rw config show',
60
66
  ' rw --version',
61
67
  ' rw changelog',
68
+ ' rw config get marketplace',
62
69
  ' 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',
70
+ ' rw config unset marketplace',
71
+ ' rw products history B0DV53VS61 --metrics bsr,price --bucket week',
72
+ ' rw products history B0DV53VS61 --startAt 2025-01-01 --endAt 2025-12-31 --bucket month',
65
73
  ' rankwrangler license status',
66
74
  ];
67
75
  console.log(usage.join('\n'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankwrangler/cli",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Official RankWrangler command line interface",
5
5
  "type": "module",
6
6
  "bin": {
@@ -20,15 +20,16 @@
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
+ "@rankwrangler/http-client": "^0.6.0",
28
+ "keytar": "7.9.0"
28
29
  },
29
30
  "devDependencies": {
30
- "@types/node": "^24.3.0",
31
- "typescript": "^5.9.2"
31
+ "@types/node": "24.10.12",
32
+ "typescript": "5.9.3"
32
33
  },
33
34
  "packageManager": "bun@1.1.27"
34
35
  }