@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.
- package/README.md +15 -59
- package/dist/CHANGELOG.md +56 -1
- package/dist/cli-auth-error.d.ts +5 -0
- package/dist/cli-auth-error.js +10 -0
- package/dist/cli-auth.d.ts +36 -15
- package/dist/cli-auth.js +80 -37
- package/dist/cli-config-command.d.ts +83 -1
- package/dist/cli-config-command.js +72 -10
- package/dist/cli-config.d.ts +8 -1
- package/dist/cli-config.js +20 -2
- package/dist/cli-keyword-command.d.ts +9 -0
- package/dist/cli-keyword-command.js +69 -0
- package/dist/cli-options.d.ts +10 -6
- package/dist/cli-options.js +43 -23
- package/dist/cli-output.js +1 -1
- package/dist/cli-product-command.d.ts +10 -0
- package/dist/cli-product-command.js +62 -0
- package/dist/history-options.d.ts +4 -0
- package/dist/history-options.js +2 -0
- package/dist/index.js +34 -67
- package/dist/usage.js +34 -23
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -1,68 +1,24 @@
|
|
|
1
1
|
# @rankwrangler/cli
|
|
2
2
|
|
|
3
|
-
Official CLI for RankWrangler.
|
|
4
|
-
|
|
5
|
-
## Install
|
|
3
|
+
Official agent-oriented CLI for RankWrangler.
|
|
6
4
|
|
|
7
5
|
```bash
|
|
8
6
|
npm install -g @rankwrangler/cli
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Usage
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
rw --version
|
|
15
|
-
rw changelog
|
|
16
|
-
rw auth set rrk_...
|
|
17
|
-
rw config set storage-dir ~/.config/rankwrangler
|
|
18
|
-
rw products get B0DV53VS61
|
|
19
|
-
rw products history B0DV53VS61 --metrics bsr,price
|
|
20
|
-
RR_LICENSE_KEY=rrk_... rw license status
|
|
21
|
-
# `rankwrangler` is also supported as an alias
|
|
22
|
-
```
|
|
23
|
-
|
|
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.
|
|
32
|
-
`rw --version` prints the installed CLI version. `rw changelog` prints the latest bundled release
|
|
33
|
-
notes shipped with the package.
|
|
34
|
-
|
|
35
|
-
## Development
|
|
36
7
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
8
|
+
rw auth set ak_...
|
|
9
|
+
rw product get B0DV53VS61
|
|
10
|
+
rw product search "retro gardening shirt"
|
|
11
|
+
rw product history B0DV53VS61 --bucket week
|
|
12
|
+
rw keyword get "retro gardening shirt"
|
|
13
|
+
rw keyword search "retro gardening"
|
|
14
|
+
rw keyword history "retro gardening shirt" --rangeDays 90
|
|
40
15
|
```
|
|
41
16
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
bun install
|
|
49
|
-
bun run release:collect-changelog-context
|
|
50
|
-
# draft CHANGELOG.md entry from commit context
|
|
51
|
-
bun run release:check
|
|
52
|
-
bun run cli:build
|
|
53
|
-
bun run release:check-cli-pack
|
|
54
|
-
```
|
|
17
|
+
The public command surface is Product `get/search/history`, keyword `get/search/history`, auth,
|
|
18
|
+
config, version, and changelog. Product `get/history` and keyword reads wait for policy-current
|
|
19
|
+
data and expose no refresh control; Product search retains its separate search contract. Search
|
|
20
|
+
data contains `keyword`, `searchedAt`, and compact results with `organicSearchPlacement`. Output is
|
|
21
|
+
a JSON `{ ok, data }` envelope; failures use `{ ok: false, error }` on stderr.
|
|
22
|
+
The CLI does not expose Catalog, Operation, provider status, or polling commands.
|
|
55
23
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Publish `packages/http-client` first. `packages/cli` now fails fast on `npm publish` until the
|
|
59
|
-
matching `@rankwrangler/http-client` version is already available on npm.
|
|
60
|
-
|
|
61
|
-
```bash
|
|
62
|
-
cd packages/cli
|
|
63
|
-
set -a
|
|
64
|
-
source ../../.env
|
|
65
|
-
set +a
|
|
66
|
-
npm whoami --userconfig ../../.npmrc
|
|
67
|
-
npm publish --access public --userconfig ../../.npmrc
|
|
68
|
-
```
|
|
24
|
+
See the [CLI reference](../../docs/reference/cli.md) for options and release workflow.
|
package/dist/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## v0.8.0 - 2026-08-09
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Add synchronous basic Product lookup for up to 200 marketplace/ASIN pairs through the public
|
|
10
|
+
API and hosted MCP, with smart single- or multi-ASIN routing in `rw product get`.
|
|
11
|
+
- Add complete Product Search, source provenance, and nullable merch-listing classification to the
|
|
12
|
+
public Product intelligence surface.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Resolve public Product reads to policy-current, source-attributed results while batching cold
|
|
17
|
+
SP-API catalog work by marketplace and persisting every requested Product identity.
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
|
|
21
|
+
- Preserve unavailable listing data, stop repeatedly polling unavailable listings, and keep those
|
|
22
|
+
Products accessible from the dashboard drawer.
|
|
23
|
+
- Expose the hosted MCP ingress correctly in production.
|
|
24
|
+
|
|
25
|
+
## v0.7.0 - 2026-08-06
|
|
26
|
+
|
|
27
|
+
### Added
|
|
28
|
+
|
|
29
|
+
- Add typed Product and keyword `get`, `search`, and `history` contracts for agents and integrations.
|
|
30
|
+
- Add Merchbase OAuth bearer authentication and centralized RankWrangler access metering across
|
|
31
|
+
public clients.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- Breaking: replace the previous public client surface with the grouped `product.get`,
|
|
36
|
+
`product.search`, `product.history`, `keyword.get`, `keyword.search`, and `keyword.history`
|
|
37
|
+
procedures.
|
|
38
|
+
- Return final source-attributed data and freshness through the public API while keeping Catalog,
|
|
39
|
+
Operation, polling, and provider orchestration internal.
|
|
40
|
+
|
|
41
|
+
## v0.6.0 - 2026-06-15
|
|
42
|
+
|
|
43
|
+
### Added
|
|
44
|
+
|
|
45
|
+
- Add `rw auth set --stdin` and hidden local auth entry so agents can pipe license keys and humans
|
|
46
|
+
can avoid shell history.
|
|
47
|
+
- Add `rw config get`, `rw config unset`, and `rw config reset` for explicit non-secret config
|
|
48
|
+
inspection and cleanup.
|
|
49
|
+
|
|
50
|
+
### Changed
|
|
51
|
+
|
|
52
|
+
- Show auth source/status in `rw config show` without printing secrets.
|
|
53
|
+
- Update CLI help, README, CLI spec, and e2e coverage for the clearer auth/config surface.
|
|
54
|
+
|
|
55
|
+
### Removed
|
|
56
|
+
|
|
57
|
+
- Remove `rw config clear` in favor of `rw config unset <key>` and `rw config reset`.
|
|
58
|
+
|
|
5
59
|
## v0.5.0 - 2026-03-18
|
|
6
60
|
|
|
7
61
|
### Added
|
|
@@ -202,4 +256,5 @@ All notable changes to this project will be documented in this file.
|
|
|
202
256
|
|
|
203
257
|
- Rename and standardize the typed npm client package to `@merchbase/rankwrangler-http-client`.
|
|
204
258
|
- Set initial HTTP client package release to `0.1.0` in `packages/http-client/package.json`.
|
|
205
|
-
- Document npm publish and versioning policy for the typed HTTP client
|
|
259
|
+
- Document npm publish and versioning policy for the typed HTTP client; the current owner is
|
|
260
|
+
`docs/reference/http-client.md`.
|
package/dist/cli-auth.d.ts
CHANGED
|
@@ -1,18 +1,16 @@
|
|
|
1
|
-
export declare const
|
|
2
|
-
export declare const AUTH_SERVICE_NAME = "
|
|
3
|
-
export declare const AUTH_ACCOUNT_NAME = "
|
|
1
|
+
export declare const API_KEY_ENV_VAR = "MERCHBASE_API_KEY";
|
|
2
|
+
export declare const AUTH_SERVICE_NAME = "co.merchbase.cli";
|
|
3
|
+
export declare const AUTH_ACCOUNT_NAME = "api-key";
|
|
4
4
|
type CliFail = (code: string, message: string, details?: unknown) => never;
|
|
5
5
|
type AuthCommand = {
|
|
6
6
|
verb: string;
|
|
7
7
|
args: string[];
|
|
8
8
|
};
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
constructor(code: string, message: string, details?: unknown);
|
|
13
|
-
}
|
|
9
|
+
type AuthCommandOptions = {
|
|
10
|
+
stdin?: boolean;
|
|
11
|
+
};
|
|
14
12
|
export declare const resolveApiKey: () => Promise<string | undefined>;
|
|
15
|
-
export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Promise<{
|
|
13
|
+
export declare const runAuthCommand: (command: AuthCommand, fail: CliFail, options?: AuthCommandOptions) => Promise<{
|
|
16
14
|
source: string;
|
|
17
15
|
envOverride: boolean;
|
|
18
16
|
secureStore: {
|
|
@@ -20,7 +18,7 @@ export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Pr
|
|
|
20
18
|
serviceName: string;
|
|
21
19
|
accountName: string;
|
|
22
20
|
available: boolean;
|
|
23
|
-
|
|
21
|
+
hasStoredApiKey: boolean;
|
|
24
22
|
error?: undefined;
|
|
25
23
|
};
|
|
26
24
|
} | {
|
|
@@ -31,7 +29,7 @@ export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Pr
|
|
|
31
29
|
serviceName: string;
|
|
32
30
|
accountName: string;
|
|
33
31
|
available: boolean;
|
|
34
|
-
|
|
32
|
+
hasStoredApiKey: null;
|
|
35
33
|
error: string;
|
|
36
34
|
};
|
|
37
35
|
} | {
|
|
@@ -43,7 +41,7 @@ export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Pr
|
|
|
43
41
|
serviceName: string;
|
|
44
42
|
accountName: string;
|
|
45
43
|
available: boolean;
|
|
46
|
-
|
|
44
|
+
hasStoredApiKey: boolean;
|
|
47
45
|
error?: undefined;
|
|
48
46
|
};
|
|
49
47
|
} | {
|
|
@@ -55,7 +53,7 @@ export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Pr
|
|
|
55
53
|
serviceName: string;
|
|
56
54
|
accountName: string;
|
|
57
55
|
available: boolean;
|
|
58
|
-
|
|
56
|
+
hasStoredApiKey: null;
|
|
59
57
|
error: string;
|
|
60
58
|
};
|
|
61
59
|
} | {
|
|
@@ -67,7 +65,7 @@ export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Pr
|
|
|
67
65
|
serviceName: string;
|
|
68
66
|
accountName: string;
|
|
69
67
|
available: boolean;
|
|
70
|
-
|
|
68
|
+
hasStoredApiKey: boolean;
|
|
71
69
|
error?: undefined;
|
|
72
70
|
};
|
|
73
71
|
} | {
|
|
@@ -79,7 +77,30 @@ export declare const runAuthCommand: (command: AuthCommand, fail: CliFail) => Pr
|
|
|
79
77
|
serviceName: string;
|
|
80
78
|
accountName: string;
|
|
81
79
|
available: boolean;
|
|
82
|
-
|
|
80
|
+
hasStoredApiKey: null;
|
|
81
|
+
error: string;
|
|
82
|
+
};
|
|
83
|
+
}>;
|
|
84
|
+
export declare const buildAuthStatus: () => Promise<{
|
|
85
|
+
source: string;
|
|
86
|
+
envOverride: boolean;
|
|
87
|
+
secureStore: {
|
|
88
|
+
backend: string;
|
|
89
|
+
serviceName: string;
|
|
90
|
+
accountName: string;
|
|
91
|
+
available: boolean;
|
|
92
|
+
hasStoredApiKey: boolean;
|
|
93
|
+
error?: undefined;
|
|
94
|
+
};
|
|
95
|
+
} | {
|
|
96
|
+
source: string;
|
|
97
|
+
envOverride: boolean;
|
|
98
|
+
secureStore: {
|
|
99
|
+
backend: string;
|
|
100
|
+
serviceName: string;
|
|
101
|
+
accountName: string;
|
|
102
|
+
available: boolean;
|
|
103
|
+
hasStoredApiKey: null;
|
|
83
104
|
error: string;
|
|
84
105
|
};
|
|
85
106
|
}>;
|
package/dist/cli-auth.js
CHANGED
|
@@ -1,33 +1,27 @@
|
|
|
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';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import readline from 'node:readline';
|
|
5
|
+
import { MERCHBASE_API_KEY_ENV, MERCHBASE_API_KEY_KEYCHAIN_ACCOUNT, MERCHBASE_API_KEY_KEYCHAIN_SERVICE, } from '@merchbaseco/access';
|
|
6
|
+
import { CliAuthError } from './cli-auth-error.js';
|
|
7
|
+
export const API_KEY_ENV_VAR = MERCHBASE_API_KEY_ENV;
|
|
8
|
+
export const AUTH_SERVICE_NAME = MERCHBASE_API_KEY_KEYCHAIN_SERVICE;
|
|
9
|
+
export const AUTH_ACCOUNT_NAME = MERCHBASE_API_KEY_KEYCHAIN_ACCOUNT;
|
|
6
10
|
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
11
|
export const resolveApiKey = async () => {
|
|
18
|
-
const
|
|
19
|
-
if (
|
|
20
|
-
return
|
|
12
|
+
const envApiKey = resolveEnvApiKey();
|
|
13
|
+
if (envApiKey) {
|
|
14
|
+
return envApiKey;
|
|
21
15
|
}
|
|
22
16
|
const secureStore = await createSecureStore();
|
|
23
17
|
return (await secureStore.get()) ?? undefined;
|
|
24
18
|
};
|
|
25
|
-
export const runAuthCommand = async (command, fail) => {
|
|
19
|
+
export const runAuthCommand = async (command, fail, options = {}) => {
|
|
26
20
|
if (command.verb === 'status') {
|
|
27
21
|
return buildAuthStatus();
|
|
28
22
|
}
|
|
29
23
|
if (command.verb === 'set') {
|
|
30
|
-
const value = resolveAuthSetValue(command.args, fail);
|
|
24
|
+
const value = await resolveAuthSetValue(command.args, fail, options);
|
|
31
25
|
const secureStore = await createSecureStore();
|
|
32
26
|
await secureStore.set(value);
|
|
33
27
|
return {
|
|
@@ -45,51 +39,100 @@ export const runAuthCommand = async (command, fail) => {
|
|
|
45
39
|
}
|
|
46
40
|
fail('UNKNOWN_COMMAND', 'Unknown auth command', { verb: command.verb });
|
|
47
41
|
};
|
|
48
|
-
const buildAuthStatus = async () => {
|
|
49
|
-
const
|
|
42
|
+
export const buildAuthStatus = async () => {
|
|
43
|
+
const envApiKey = resolveEnvApiKey();
|
|
50
44
|
try {
|
|
51
45
|
const secureStore = await createSecureStore();
|
|
52
|
-
const
|
|
46
|
+
const storedApiKey = await secureStore.get();
|
|
53
47
|
return {
|
|
54
|
-
source:
|
|
55
|
-
envOverride: Boolean(
|
|
48
|
+
source: envApiKey ? 'env' : storedApiKey ? 'secure-store' : 'none',
|
|
49
|
+
envOverride: Boolean(envApiKey),
|
|
56
50
|
secureStore: {
|
|
57
51
|
backend: secureStore.backend,
|
|
58
52
|
serviceName: secureStore.serviceName,
|
|
59
53
|
accountName: secureStore.accountName,
|
|
60
54
|
available: true,
|
|
61
|
-
|
|
55
|
+
hasStoredApiKey: Boolean(storedApiKey),
|
|
62
56
|
},
|
|
63
57
|
};
|
|
64
58
|
}
|
|
65
59
|
catch (error) {
|
|
66
60
|
return {
|
|
67
|
-
source:
|
|
68
|
-
envOverride: Boolean(
|
|
61
|
+
source: envApiKey ? 'env' : 'none',
|
|
62
|
+
envOverride: Boolean(envApiKey),
|
|
69
63
|
secureStore: {
|
|
70
64
|
backend: getSecureStoreBackendLabel(),
|
|
71
65
|
serviceName: AUTH_SERVICE_NAME,
|
|
72
66
|
accountName: AUTH_ACCOUNT_NAME,
|
|
73
67
|
available: false,
|
|
74
|
-
|
|
68
|
+
hasStoredApiKey: null,
|
|
75
69
|
error: error instanceof Error ? error.message : 'Unknown secure-store error',
|
|
76
70
|
},
|
|
77
71
|
};
|
|
78
72
|
}
|
|
79
73
|
};
|
|
80
|
-
const resolveAuthSetValue = (args, fail) => {
|
|
74
|
+
const resolveAuthSetValue = async (args, fail, options) => {
|
|
81
75
|
const positionalValue = args.join(' ').trim();
|
|
82
76
|
if (positionalValue) {
|
|
83
77
|
return positionalValue;
|
|
84
78
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
79
|
+
if (options.stdin) {
|
|
80
|
+
const stdinValue = await readSecretFromStdin();
|
|
81
|
+
if (stdinValue) {
|
|
82
|
+
return stdinValue;
|
|
83
|
+
}
|
|
84
|
+
fail('INVALID_INPUT', 'auth set --stdin received an empty API key');
|
|
88
85
|
}
|
|
89
|
-
|
|
86
|
+
const envApiKey = resolveEnvApiKey();
|
|
87
|
+
if (envApiKey) {
|
|
88
|
+
return envApiKey;
|
|
89
|
+
}
|
|
90
|
+
if (process.stdin.isTTY && process.stderr.isTTY) {
|
|
91
|
+
const promptValue = await readSecretFromPrompt('Merchbase API key: ');
|
|
92
|
+
if (promptValue) {
|
|
93
|
+
return promptValue;
|
|
94
|
+
}
|
|
95
|
+
fail('INVALID_INPUT', 'auth set received an empty API key');
|
|
96
|
+
}
|
|
97
|
+
fail('INVALID_INPUT', `auth set requires <apiKey>, --stdin, or ${API_KEY_ENV_VAR}`);
|
|
98
|
+
};
|
|
99
|
+
const readSecretFromStdin = async () => {
|
|
100
|
+
const chunks = [];
|
|
101
|
+
for await (const chunk of process.stdin) {
|
|
102
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
103
|
+
}
|
|
104
|
+
return Buffer.concat(chunks).toString('utf8').trim();
|
|
105
|
+
};
|
|
106
|
+
const readSecretFromPrompt = async (prompt) => {
|
|
107
|
+
process.stderr.write(prompt);
|
|
108
|
+
setTerminalEcho(false);
|
|
109
|
+
const rl = readline.createInterface({
|
|
110
|
+
input: process.stdin,
|
|
111
|
+
output: process.stderr,
|
|
112
|
+
terminal: true,
|
|
113
|
+
});
|
|
114
|
+
try {
|
|
115
|
+
const value = await new Promise(resolve => {
|
|
116
|
+
rl.question('', resolve);
|
|
117
|
+
});
|
|
118
|
+
process.stderr.write('\n');
|
|
119
|
+
return value.trim();
|
|
120
|
+
}
|
|
121
|
+
finally {
|
|
122
|
+
rl.close();
|
|
123
|
+
setTerminalEcho(true);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
const setTerminalEcho = (enabled) => {
|
|
127
|
+
if (process.platform === 'win32' || !process.stdin.isTTY) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
spawnSync('stty', [enabled ? 'echo' : '-echo'], {
|
|
131
|
+
stdio: ['inherit', 'ignore', 'ignore'],
|
|
132
|
+
});
|
|
90
133
|
};
|
|
91
|
-
const
|
|
92
|
-
const value = process.env[
|
|
134
|
+
const resolveEnvApiKey = () => {
|
|
135
|
+
const value = process.env[API_KEY_ENV_VAR]?.trim();
|
|
93
136
|
return value ? value : undefined;
|
|
94
137
|
};
|
|
95
138
|
const createSecureStore = async () => {
|
|
@@ -127,7 +170,7 @@ const getSecureStoreBackendLabel = () => {
|
|
|
127
170
|
}
|
|
128
171
|
};
|
|
129
172
|
const createFileSecretStore = (secretStoreDir) => {
|
|
130
|
-
const secretPath = path.join(secretStoreDir, '
|
|
173
|
+
const secretPath = path.join(secretStoreDir, 'api-key.json');
|
|
131
174
|
return {
|
|
132
175
|
backend: 'test-file-store',
|
|
133
176
|
serviceName: AUTH_SERVICE_NAME,
|
|
@@ -136,7 +179,7 @@ const createFileSecretStore = (secretStoreDir) => {
|
|
|
136
179
|
try {
|
|
137
180
|
const raw = await readFile(secretPath, 'utf8');
|
|
138
181
|
const parsed = JSON.parse(raw);
|
|
139
|
-
return typeof parsed.
|
|
182
|
+
return typeof parsed.apiKey === 'string' ? parsed.apiKey : null;
|
|
140
183
|
}
|
|
141
184
|
catch (error) {
|
|
142
185
|
if (error &&
|
|
@@ -150,7 +193,7 @@ const createFileSecretStore = (secretStoreDir) => {
|
|
|
150
193
|
},
|
|
151
194
|
set: async (value) => {
|
|
152
195
|
await mkdir(secretStoreDir, { recursive: true });
|
|
153
|
-
await writeFile(secretPath, `${JSON.stringify({
|
|
196
|
+
await writeFile(secretPath, `${JSON.stringify({ apiKey: value }, null, 2)}\n`, 'utf8');
|
|
154
197
|
},
|
|
155
198
|
clear: async () => {
|
|
156
199
|
try {
|
|
@@ -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
|
+
hasStoredApiKey: 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
|
+
hasStoredApiKey: null;
|
|
32
|
+
error: string;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
12
35
|
} | {
|
|
13
|
-
|
|
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
|
+
hasStoredApiKey: 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
|
+
hasStoredApiKey: 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
|
+
hasStoredApiKey: 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
|
+
hasStoredApiKey: null;
|
|
95
|
+
error: string;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
key?: undefined;
|
|
99
|
+
value?: undefined;
|
|
18
100
|
}>;
|
|
19
101
|
export {};
|
|
@@ -1,14 +1,31 @@
|
|
|
1
|
-
import {
|
|
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 === '
|
|
8
|
-
|
|
9
|
+
if (command.verb === 'get') {
|
|
10
|
+
const key = requireConfigKey(command.args[0], 'config get', fail);
|
|
9
11
|
return {
|
|
10
|
-
|
|
11
|
-
|
|
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
|
-
|
|
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
|
+
};
|
package/dist/cli-config.d.ts
CHANGED
|
@@ -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
|
|
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;
|