@rankwrangler/cli 0.4.1 → 0.5.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 +10 -5
- package/dist/CHANGELOG.md +18 -0
- package/dist/cli-auth.d.ts +86 -0
- package/dist/cli-auth.js +171 -0
- package/dist/cli-config-command.d.ts +19 -0
- package/dist/cli-config-command.js +56 -0
- package/dist/cli-options.d.ts +0 -1
- package/dist/cli-options.js +0 -3
- package/dist/cli-output.js +8 -0
- package/dist/index.js +14 -59
- package/dist/usage.js +7 -2
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -11,19 +11,24 @@ npm install -g @rankwrangler/cli
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
export RR_LICENSE_KEY=rrk_...
|
|
15
14
|
rw --version
|
|
16
15
|
rw changelog
|
|
16
|
+
rw auth set rrk_...
|
|
17
17
|
rw config set storage-dir ~/.config/rankwrangler
|
|
18
18
|
rw products get B0DV53VS61
|
|
19
19
|
rw products history B0DV53VS61 --metrics bsr,price
|
|
20
|
+
RR_LICENSE_KEY=rrk_... rw license status
|
|
20
21
|
# `rankwrangler` is also supported as an alias
|
|
21
22
|
```
|
|
22
23
|
|
|
23
|
-
`
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
`rw auth set <licenseKey>` stores the license key in the platform secure store
|
|
25
|
+
(`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.
|
|
27
|
+
|
|
28
|
+
`rw config set storage-dir <path>` saves the active storage directory globally. After that, every
|
|
29
|
+
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.
|
|
27
32
|
`rw --version` prints the installed CLI version. `rw changelog` prints the latest bundled release
|
|
28
33
|
notes shipped with the package.
|
|
29
34
|
|
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.5.0 - 2026-03-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Add CLI secure-store auth with `rw auth set`, `rw auth status`, and `rw auth clear`, storing
|
|
10
|
+
license keys in the platform secure store while keeping `RR_LICENSE_KEY` as an override for
|
|
11
|
+
CI, automation, and agent runtimes.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- Change local development defaults so the app server no longer starts the job runner unless you
|
|
16
|
+
use the explicit jobs-enabled dev commands.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- Fix top search terms scheduler timestamp handling so due datasets are selected and refreshed
|
|
21
|
+
reliably.
|
|
22
|
+
|
|
5
23
|
## v0.4.1 - 2026-03-09
|
|
6
24
|
|
|
7
25
|
### Added
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export declare const LICENSE_KEY_ENV_VAR = "RR_LICENSE_KEY";
|
|
2
|
+
export declare const AUTH_SERVICE_NAME = "RankWrangler CLI";
|
|
3
|
+
export declare const AUTH_ACCOUNT_NAME = "license-key";
|
|
4
|
+
type CliFail = (code: string, message: string, details?: unknown) => never;
|
|
5
|
+
type AuthCommand = {
|
|
6
|
+
verb: string;
|
|
7
|
+
args: string[];
|
|
8
|
+
};
|
|
9
|
+
export declare class CliAuthError extends Error {
|
|
10
|
+
readonly code: string;
|
|
11
|
+
readonly details?: unknown;
|
|
12
|
+
constructor(code: string, message: string, details?: unknown);
|
|
13
|
+
}
|
|
14
|
+
export declare const resolveApiKey: () => Promise<string | undefined>;
|
|
15
|
+
export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Promise<{
|
|
16
|
+
source: string;
|
|
17
|
+
envOverride: boolean;
|
|
18
|
+
secureStore: {
|
|
19
|
+
backend: string;
|
|
20
|
+
serviceName: string;
|
|
21
|
+
accountName: string;
|
|
22
|
+
available: boolean;
|
|
23
|
+
hasStoredLicenseKey: boolean;
|
|
24
|
+
error?: undefined;
|
|
25
|
+
};
|
|
26
|
+
} | {
|
|
27
|
+
source: string;
|
|
28
|
+
envOverride: boolean;
|
|
29
|
+
secureStore: {
|
|
30
|
+
backend: string;
|
|
31
|
+
serviceName: string;
|
|
32
|
+
accountName: string;
|
|
33
|
+
available: boolean;
|
|
34
|
+
hasStoredLicenseKey: null;
|
|
35
|
+
error: string;
|
|
36
|
+
};
|
|
37
|
+
} | {
|
|
38
|
+
saved: boolean;
|
|
39
|
+
source: string;
|
|
40
|
+
envOverride: boolean;
|
|
41
|
+
secureStore: {
|
|
42
|
+
backend: string;
|
|
43
|
+
serviceName: string;
|
|
44
|
+
accountName: string;
|
|
45
|
+
available: boolean;
|
|
46
|
+
hasStoredLicenseKey: boolean;
|
|
47
|
+
error?: undefined;
|
|
48
|
+
};
|
|
49
|
+
} | {
|
|
50
|
+
saved: boolean;
|
|
51
|
+
source: string;
|
|
52
|
+
envOverride: boolean;
|
|
53
|
+
secureStore: {
|
|
54
|
+
backend: string;
|
|
55
|
+
serviceName: string;
|
|
56
|
+
accountName: string;
|
|
57
|
+
available: boolean;
|
|
58
|
+
hasStoredLicenseKey: null;
|
|
59
|
+
error: string;
|
|
60
|
+
};
|
|
61
|
+
} | {
|
|
62
|
+
cleared: boolean;
|
|
63
|
+
source: string;
|
|
64
|
+
envOverride: boolean;
|
|
65
|
+
secureStore: {
|
|
66
|
+
backend: string;
|
|
67
|
+
serviceName: string;
|
|
68
|
+
accountName: string;
|
|
69
|
+
available: boolean;
|
|
70
|
+
hasStoredLicenseKey: boolean;
|
|
71
|
+
error?: undefined;
|
|
72
|
+
};
|
|
73
|
+
} | {
|
|
74
|
+
cleared: boolean;
|
|
75
|
+
source: string;
|
|
76
|
+
envOverride: boolean;
|
|
77
|
+
secureStore: {
|
|
78
|
+
backend: string;
|
|
79
|
+
serviceName: string;
|
|
80
|
+
accountName: string;
|
|
81
|
+
available: boolean;
|
|
82
|
+
hasStoredLicenseKey: null;
|
|
83
|
+
error: string;
|
|
84
|
+
};
|
|
85
|
+
}>;
|
|
86
|
+
export {};
|
package/dist/cli-auth.js
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export const LICENSE_KEY_ENV_VAR = 'RR_LICENSE_KEY';
|
|
4
|
+
export const AUTH_SERVICE_NAME = 'RankWrangler CLI';
|
|
5
|
+
export const AUTH_ACCOUNT_NAME = 'license-key';
|
|
6
|
+
const INTERNAL_SECRET_STORE_DIR_ENV_VAR = 'RW_INTERNAL_CLI_SECRET_STORE_DIR';
|
|
7
|
+
export class CliAuthError extends Error {
|
|
8
|
+
code;
|
|
9
|
+
details;
|
|
10
|
+
constructor(code, message, details) {
|
|
11
|
+
super(message);
|
|
12
|
+
this.name = 'CliAuthError';
|
|
13
|
+
this.code = code;
|
|
14
|
+
this.details = details;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export const resolveApiKey = async () => {
|
|
18
|
+
const envLicenseKey = resolveEnvLicenseKey();
|
|
19
|
+
if (envLicenseKey) {
|
|
20
|
+
return envLicenseKey;
|
|
21
|
+
}
|
|
22
|
+
const secureStore = await createSecureStore();
|
|
23
|
+
return (await secureStore.get()) ?? undefined;
|
|
24
|
+
};
|
|
25
|
+
export const runAuthCommand = async (command, fail) => {
|
|
26
|
+
if (command.verb === 'status') {
|
|
27
|
+
return buildAuthStatus();
|
|
28
|
+
}
|
|
29
|
+
if (command.verb === 'set') {
|
|
30
|
+
const value = resolveAuthSetValue(command.args, fail);
|
|
31
|
+
const secureStore = await createSecureStore();
|
|
32
|
+
await secureStore.set(value);
|
|
33
|
+
return {
|
|
34
|
+
...(await buildAuthStatus()),
|
|
35
|
+
saved: true,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
if (command.verb === 'clear') {
|
|
39
|
+
const secureStore = await createSecureStore();
|
|
40
|
+
const cleared = await secureStore.clear();
|
|
41
|
+
return {
|
|
42
|
+
...(await buildAuthStatus()),
|
|
43
|
+
cleared,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
fail('UNKNOWN_COMMAND', 'Unknown auth command', { verb: command.verb });
|
|
47
|
+
};
|
|
48
|
+
const buildAuthStatus = async () => {
|
|
49
|
+
const envLicenseKey = resolveEnvLicenseKey();
|
|
50
|
+
try {
|
|
51
|
+
const secureStore = await createSecureStore();
|
|
52
|
+
const storedLicenseKey = await secureStore.get();
|
|
53
|
+
return {
|
|
54
|
+
source: envLicenseKey ? 'env' : storedLicenseKey ? 'secure-store' : 'none',
|
|
55
|
+
envOverride: Boolean(envLicenseKey),
|
|
56
|
+
secureStore: {
|
|
57
|
+
backend: secureStore.backend,
|
|
58
|
+
serviceName: secureStore.serviceName,
|
|
59
|
+
accountName: secureStore.accountName,
|
|
60
|
+
available: true,
|
|
61
|
+
hasStoredLicenseKey: Boolean(storedLicenseKey),
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
return {
|
|
67
|
+
source: envLicenseKey ? 'env' : 'none',
|
|
68
|
+
envOverride: Boolean(envLicenseKey),
|
|
69
|
+
secureStore: {
|
|
70
|
+
backend: getSecureStoreBackendLabel(),
|
|
71
|
+
serviceName: AUTH_SERVICE_NAME,
|
|
72
|
+
accountName: AUTH_ACCOUNT_NAME,
|
|
73
|
+
available: false,
|
|
74
|
+
hasStoredLicenseKey: null,
|
|
75
|
+
error: error instanceof Error ? error.message : 'Unknown secure-store error',
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
const resolveAuthSetValue = (args, fail) => {
|
|
81
|
+
const positionalValue = args.join(' ').trim();
|
|
82
|
+
if (positionalValue) {
|
|
83
|
+
return positionalValue;
|
|
84
|
+
}
|
|
85
|
+
const envLicenseKey = resolveEnvLicenseKey();
|
|
86
|
+
if (envLicenseKey) {
|
|
87
|
+
return envLicenseKey;
|
|
88
|
+
}
|
|
89
|
+
fail('INVALID_INPUT', `auth set requires <licenseKey> or ${LICENSE_KEY_ENV_VAR}`);
|
|
90
|
+
};
|
|
91
|
+
const resolveEnvLicenseKey = () => {
|
|
92
|
+
const value = process.env[LICENSE_KEY_ENV_VAR]?.trim();
|
|
93
|
+
return value ? value : undefined;
|
|
94
|
+
};
|
|
95
|
+
const createSecureStore = async () => {
|
|
96
|
+
const internalSecretStoreDir = process.env[INTERNAL_SECRET_STORE_DIR_ENV_VAR]?.trim();
|
|
97
|
+
if (internalSecretStoreDir) {
|
|
98
|
+
return createFileSecretStore(path.resolve(internalSecretStoreDir));
|
|
99
|
+
}
|
|
100
|
+
const keytar = await loadKeytar();
|
|
101
|
+
return {
|
|
102
|
+
backend: getSecureStoreBackendLabel(),
|
|
103
|
+
serviceName: AUTH_SERVICE_NAME,
|
|
104
|
+
accountName: AUTH_ACCOUNT_NAME,
|
|
105
|
+
get: () => keytar.getPassword(AUTH_SERVICE_NAME, AUTH_ACCOUNT_NAME),
|
|
106
|
+
set: value => keytar.setPassword(AUTH_SERVICE_NAME, AUTH_ACCOUNT_NAME, value),
|
|
107
|
+
clear: () => keytar.deletePassword(AUTH_SERVICE_NAME, AUTH_ACCOUNT_NAME),
|
|
108
|
+
};
|
|
109
|
+
};
|
|
110
|
+
const loadKeytar = async () => {
|
|
111
|
+
try {
|
|
112
|
+
const imported = (await import('keytar'));
|
|
113
|
+
return imported.default ?? imported;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
throw new CliAuthError('SECURE_STORE_UNAVAILABLE', 'platform secure store is unavailable', error instanceof Error ? { cause: error.message } : undefined);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const getSecureStoreBackendLabel = () => {
|
|
120
|
+
switch (process.platform) {
|
|
121
|
+
case 'darwin':
|
|
122
|
+
return 'macos-keychain';
|
|
123
|
+
case 'win32':
|
|
124
|
+
return 'windows-credential-manager';
|
|
125
|
+
default:
|
|
126
|
+
return 'platform-secure-store';
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const createFileSecretStore = (secretStoreDir) => {
|
|
130
|
+
const secretPath = path.join(secretStoreDir, 'license-key.json');
|
|
131
|
+
return {
|
|
132
|
+
backend: 'test-file-store',
|
|
133
|
+
serviceName: AUTH_SERVICE_NAME,
|
|
134
|
+
accountName: AUTH_ACCOUNT_NAME,
|
|
135
|
+
get: async () => {
|
|
136
|
+
try {
|
|
137
|
+
const raw = await readFile(secretPath, 'utf8');
|
|
138
|
+
const parsed = JSON.parse(raw);
|
|
139
|
+
return typeof parsed.licenseKey === 'string' ? parsed.licenseKey : null;
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
if (error &&
|
|
143
|
+
typeof error === 'object' &&
|
|
144
|
+
'code' in error &&
|
|
145
|
+
error.code === 'ENOENT') {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
throw new CliAuthError('INVALID_CONFIG', 'failed to read secure-store test data');
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
set: async (value) => {
|
|
152
|
+
await mkdir(secretStoreDir, { recursive: true });
|
|
153
|
+
await writeFile(secretPath, `${JSON.stringify({ licenseKey: value }, null, 2)}\n`, 'utf8');
|
|
154
|
+
},
|
|
155
|
+
clear: async () => {
|
|
156
|
+
try {
|
|
157
|
+
await rm(secretPath);
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
if (error &&
|
|
162
|
+
typeof error === 'object' &&
|
|
163
|
+
'code' in error &&
|
|
164
|
+
error.code === 'ENOENT') {
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type CliConfig, type CliPaths } from './cli-config';
|
|
2
|
+
type CliFail = (code: string, message: string, details?: unknown) => never;
|
|
3
|
+
type ConfigCommand = {
|
|
4
|
+
verb: string;
|
|
5
|
+
args: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare const runConfigCommand: (command: ConfigCommand, config: CliConfig, paths: CliPaths, fail: CliFail) => Promise<{
|
|
8
|
+
storageDir: string;
|
|
9
|
+
path: string;
|
|
10
|
+
globalPath: string;
|
|
11
|
+
config: CliConfig;
|
|
12
|
+
} | {
|
|
13
|
+
cleared: boolean;
|
|
14
|
+
storageDir: string;
|
|
15
|
+
path: string;
|
|
16
|
+
globalPath: string;
|
|
17
|
+
config: CliConfig;
|
|
18
|
+
}>;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { clearConfig, saveConfig, switchStorageDir } from './cli-config.js';
|
|
2
|
+
import { normalizeBaseUrl } from './cli-options.js';
|
|
3
|
+
export const runConfigCommand = async (command, config, paths, fail) => {
|
|
4
|
+
if (command.verb === 'show') {
|
|
5
|
+
return buildConfigResponse(paths, config);
|
|
6
|
+
}
|
|
7
|
+
if (command.verb === 'clear') {
|
|
8
|
+
await clearConfig(paths);
|
|
9
|
+
return {
|
|
10
|
+
...buildConfigResponse(paths, {}),
|
|
11
|
+
cleared: true,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
if (command.verb !== 'set') {
|
|
15
|
+
fail('UNKNOWN_COMMAND', 'Unknown config command', { verb: command.verb });
|
|
16
|
+
}
|
|
17
|
+
const [key, ...valueParts] = command.args;
|
|
18
|
+
if (!key || valueParts.length === 0) {
|
|
19
|
+
fail('INVALID_INPUT', 'config set requires <key> <value>');
|
|
20
|
+
}
|
|
21
|
+
const value = valueParts.join(' ').trim();
|
|
22
|
+
if (!value) {
|
|
23
|
+
fail('INVALID_INPUT', 'config set value cannot be empty');
|
|
24
|
+
}
|
|
25
|
+
if (key === 'storage-dir') {
|
|
26
|
+
const nextState = await switchStorageDir({
|
|
27
|
+
currentConfig: config,
|
|
28
|
+
currentPaths: paths,
|
|
29
|
+
requestedStorageDir: value,
|
|
30
|
+
});
|
|
31
|
+
return buildConfigResponse(nextState.paths, nextState.config);
|
|
32
|
+
}
|
|
33
|
+
const nextConfig = { ...config };
|
|
34
|
+
if (key === 'base-url') {
|
|
35
|
+
nextConfig.baseUrl = normalizeBaseUrl(value, fail);
|
|
36
|
+
}
|
|
37
|
+
else if (key === 'marketplace') {
|
|
38
|
+
nextConfig.marketplaceId = value;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
fail('INVALID_INPUT', 'unsupported config key', {
|
|
42
|
+
key,
|
|
43
|
+
supportedKeys: ['base-url', 'marketplace', 'storage-dir'],
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
await saveConfig(paths, nextConfig);
|
|
47
|
+
return buildConfigResponse(paths, nextConfig);
|
|
48
|
+
};
|
|
49
|
+
const buildConfigResponse = (paths, config) => {
|
|
50
|
+
return {
|
|
51
|
+
storageDir: paths.storageDir,
|
|
52
|
+
path: paths.configPath,
|
|
53
|
+
globalPath: paths.globalConfigPath,
|
|
54
|
+
config,
|
|
55
|
+
};
|
|
56
|
+
};
|
package/dist/cli-options.d.ts
CHANGED
|
@@ -22,7 +22,6 @@ export declare const resolveHistoryWindow: (values: CliOptionValues, fail: CliFa
|
|
|
22
22
|
endAt?: string | undefined;
|
|
23
23
|
startAt?: string | undefined;
|
|
24
24
|
};
|
|
25
|
-
export declare const resolveApiKey: () => string | undefined;
|
|
26
25
|
export declare const resolveBaseUrl: (values: CliOptionValues, config: CliConfig, defaultBaseUrl: string, fail: CliFail) => string;
|
|
27
26
|
export declare const normalizeBaseUrl: (value: string, fail: CliFail) => string;
|
|
28
27
|
export declare const parseIntegerOption: ({ value, optionName, min, max, defaultValue, }: {
|
package/dist/cli-options.js
CHANGED
|
@@ -56,9 +56,6 @@ export const resolveHistoryWindow = (values, fail) => {
|
|
|
56
56
|
}, fail),
|
|
57
57
|
};
|
|
58
58
|
};
|
|
59
|
-
export const resolveApiKey = () => {
|
|
60
|
-
return process.env.RR_LICENSE_KEY;
|
|
61
|
-
};
|
|
62
59
|
export const resolveBaseUrl = (values, config, defaultBaseUrl, fail) => {
|
|
63
60
|
return normalizeBaseUrl(values.baseUrl ?? config.baseUrl ?? process.env.RR_API_URL ?? defaultBaseUrl, fail);
|
|
64
61
|
};
|
package/dist/cli-output.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CliAuthError } from './cli-auth.js';
|
|
1
2
|
import { CliStorageError } from './cli-config.js';
|
|
2
3
|
const OUTPUT_PRETTY = true;
|
|
3
4
|
export const printSuccess = (data) => {
|
|
@@ -27,6 +28,13 @@ export const resolveError = (error) => {
|
|
|
27
28
|
details: error.details,
|
|
28
29
|
};
|
|
29
30
|
}
|
|
31
|
+
if (error instanceof CliAuthError) {
|
|
32
|
+
return {
|
|
33
|
+
code: error.code,
|
|
34
|
+
message: error.message,
|
|
35
|
+
details: error.details,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
30
38
|
if (error && typeof error === 'object' && 'data' in error) {
|
|
31
39
|
const data = error.data;
|
|
32
40
|
if (data?.code) {
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { parseArgs } from 'node:util';
|
|
3
3
|
import { createRankWranglerClient, DEFAULT_API_BASE_URL } from '@rankwrangler/http-client';
|
|
4
|
+
import { resolveApiKey, runAuthCommand } from './cli-auth.js';
|
|
4
5
|
import { printBundledChangelog, printCliVersion } from './cli-metadata.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
6
|
+
import { runConfigCommand } from './cli-config-command.js';
|
|
7
|
+
import { loadCliContext, loadCliPathsOrDefault } from './cli-config.js';
|
|
8
|
+
import { parseIntegerOption, requireAsins, requireMarketplaceId, requireSingleAsin, resolveBaseUrl, resolveHistoryMetrics, resolveHistoryWindow, } from './cli-options.js';
|
|
7
9
|
import { fail, printSuccess, resolveError } from './cli-output.js';
|
|
8
10
|
import { buildCliHistoryResponse } from './history-response.js';
|
|
9
11
|
import { printUsage } from './usage.js';
|
|
@@ -12,6 +14,9 @@ const SUPPORTED_COMMANDS = new Set([
|
|
|
12
14
|
'products:history',
|
|
13
15
|
'license:status',
|
|
14
16
|
'license:validate',
|
|
17
|
+
'auth:status',
|
|
18
|
+
'auth:set',
|
|
19
|
+
'auth:clear',
|
|
15
20
|
'config:show',
|
|
16
21
|
'config:clear',
|
|
17
22
|
'config:set',
|
|
@@ -57,12 +62,16 @@ const main = async () => {
|
|
|
57
62
|
}
|
|
58
63
|
const { config, paths } = await loadCliContext();
|
|
59
64
|
if (command.resource === 'config') {
|
|
60
|
-
printSuccess(await runConfigCommand(command, config, paths));
|
|
65
|
+
printSuccess(await runConfigCommand(command, config, paths, fail));
|
|
61
66
|
return;
|
|
62
67
|
}
|
|
63
|
-
|
|
68
|
+
if (command.resource === 'auth') {
|
|
69
|
+
printSuccess(await runAuthCommand(command, fail));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const apiKey = await resolveApiKey();
|
|
64
73
|
if (!apiKey) {
|
|
65
|
-
fail('MISSING_CONFIG', '
|
|
74
|
+
fail('MISSING_CONFIG', 'license key is required. run `rw auth set <licenseKey>` or set RR_LICENSE_KEY');
|
|
66
75
|
}
|
|
67
76
|
const client = createRankWranglerClient({
|
|
68
77
|
baseUrl: resolveBaseUrl(optionValues, config, DEFAULT_API_BASE_URL, fail),
|
|
@@ -121,60 +130,6 @@ const runProductHistoryCommand = async (command, client, config) => {
|
|
|
121
130
|
response: response,
|
|
122
131
|
});
|
|
123
132
|
};
|
|
124
|
-
const runConfigCommand = async (command, config, paths) => {
|
|
125
|
-
if (command.verb === 'show') {
|
|
126
|
-
return buildConfigResponse(paths, config);
|
|
127
|
-
}
|
|
128
|
-
if (command.verb === 'clear') {
|
|
129
|
-
await clearConfig(paths);
|
|
130
|
-
return {
|
|
131
|
-
...buildConfigResponse(paths, {}),
|
|
132
|
-
cleared: true,
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
if (command.verb !== 'set') {
|
|
136
|
-
fail('UNKNOWN_COMMAND', 'Unknown config command', { verb: command.verb });
|
|
137
|
-
}
|
|
138
|
-
const [key, ...valueParts] = command.args;
|
|
139
|
-
if (!key || valueParts.length === 0) {
|
|
140
|
-
fail('INVALID_INPUT', 'config set requires <key> <value>');
|
|
141
|
-
}
|
|
142
|
-
const value = valueParts.join(' ').trim();
|
|
143
|
-
if (!value) {
|
|
144
|
-
fail('INVALID_INPUT', 'config set value cannot be empty');
|
|
145
|
-
}
|
|
146
|
-
if (key === 'storage-dir') {
|
|
147
|
-
const nextState = await switchStorageDir({
|
|
148
|
-
currentConfig: config,
|
|
149
|
-
currentPaths: paths,
|
|
150
|
-
requestedStorageDir: value,
|
|
151
|
-
});
|
|
152
|
-
return buildConfigResponse(nextState.paths, nextState.config);
|
|
153
|
-
}
|
|
154
|
-
const nextConfig = { ...config };
|
|
155
|
-
if (key === 'base-url') {
|
|
156
|
-
nextConfig.baseUrl = normalizeBaseUrl(value, fail);
|
|
157
|
-
}
|
|
158
|
-
else if (key === 'marketplace') {
|
|
159
|
-
nextConfig.marketplaceId = value;
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
fail('INVALID_INPUT', 'unsupported config key', {
|
|
163
|
-
key,
|
|
164
|
-
supportedKeys: ['base-url', 'marketplace', 'storage-dir'],
|
|
165
|
-
});
|
|
166
|
-
}
|
|
167
|
-
await saveConfig(paths, nextConfig);
|
|
168
|
-
return buildConfigResponse(paths, nextConfig);
|
|
169
|
-
};
|
|
170
|
-
const buildConfigResponse = (paths, config) => {
|
|
171
|
-
return {
|
|
172
|
-
storageDir: paths.storageDir,
|
|
173
|
-
path: paths.configPath,
|
|
174
|
-
globalPath: paths.globalConfigPath,
|
|
175
|
-
config,
|
|
176
|
-
};
|
|
177
|
-
};
|
|
178
133
|
const resolveMetaCommand = (inputPositionals) => {
|
|
179
134
|
if (inputPositionals.length === 1 && inputPositionals[0] === 'changelog') {
|
|
180
135
|
return { name: 'changelog' };
|
package/dist/usage.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { LICENSE_KEY_ENV_VAR } from './cli-auth.js';
|
|
1
2
|
const DEFAULT_MARKETPLACE_ID = 'ATVPDKIKX0DER';
|
|
2
3
|
export const printUsage = (paths) => {
|
|
3
4
|
const usage = [
|
|
@@ -18,6 +19,9 @@ export const printUsage = (paths) => {
|
|
|
18
19
|
' products history <ASIN>',
|
|
19
20
|
' license status',
|
|
20
21
|
' license validate',
|
|
22
|
+
' auth status',
|
|
23
|
+
' auth set <licenseKey>',
|
|
24
|
+
' auth clear',
|
|
21
25
|
' config show',
|
|
22
26
|
' config clear',
|
|
23
27
|
' config set base-url <origin>',
|
|
@@ -41,7 +45,7 @@ export const printUsage = (paths) => {
|
|
|
41
45
|
` ${paths.globalConfigPath} Global CLI settings`,
|
|
42
46
|
'',
|
|
43
47
|
'ENVIRONMENT',
|
|
44
|
-
|
|
48
|
+
` ${LICENSE_KEY_ENV_VAR} License key override for CI/automation`,
|
|
45
49
|
' RR_API_URL API origin fallback',
|
|
46
50
|
' RR_MARKETPLACE_ID Marketplace fallback',
|
|
47
51
|
' RR_STORAGE_DIR Storage directory override',
|
|
@@ -50,7 +54,8 @@ export const printUsage = (paths) => {
|
|
|
50
54
|
' RR_HISTORY_METRICS Comma-separated history metrics fallback',
|
|
51
55
|
'',
|
|
52
56
|
'EXAMPLES',
|
|
53
|
-
'
|
|
57
|
+
' rw auth set rrk_...',
|
|
58
|
+
` ${LICENSE_KEY_ENV_VAR}=rrk_... rw products get B0DV53VS61`,
|
|
54
59
|
' RR_STORAGE_DIR=~/.config/rankwrangler rw config show',
|
|
55
60
|
' rw --version',
|
|
56
61
|
' rw changelog',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rankwrangler/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Official RankWrangler command line interface",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
"access": "public"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@rankwrangler/http-client": "^0.
|
|
26
|
+
"@rankwrangler/http-client": "^0.5.0",
|
|
27
|
+
"keytar": "^7.9.0"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node": "^24.3.0",
|