@rankwrangler/cli 0.11.0 → 0.13.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
@@ -13,10 +13,9 @@ rw keyword results "retro gardening shirt"
13
13
  rw product history B0DV53VS61 --bucket week
14
14
  rw keyword get "retro gardening shirt"
15
15
  rw keyword search "retro gardening"
16
- rw keyword history "retro gardening shirt" --rangeDays 90
17
16
  ```
18
17
 
19
- The public command surface is Product `get/search/history`, keyword `get/search/history/results`,
18
+ The public command surface is Product `get/search/history`, keyword `get/search/results`,
20
19
  auth, config, version, and changelog. Product search reads the collected database immediately and
21
20
  supports filters, sorting, and cursor pagination. Keyword results returns dated Amazon search
22
21
  results with placement and accepts `--refresh` to replace the observed Search run.
package/dist/CHANGELOG.md CHANGED
@@ -2,6 +2,49 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file.
4
4
 
5
+ ## v0.13.0 - 2026-10-28
6
+
7
+ ### Added
8
+
9
+ - Rebuild the website on HeroUI Pro: Explore and Keywords grids with filter sidebars and counts,
10
+ KPI bands, BSR sparklines, a pager, and a full Product page.
11
+ - Drive keywords from Brand Analytics searches and keyword themes for every Merch product type,
12
+ with five relevant keywords selected per product and collection run as gated jobs.
13
+ - Detect Merch products by part number and keep scans Merch-only; the Merch re-check can demote
14
+ false positives.
15
+ - Push keyword, sync and log changes live over the tRPC WebSocket.
16
+
17
+ ### Changed
18
+
19
+ - Breaking: public keyword reads replace `state`, `status` and `untrackedReason` with `stage`
20
+ (`graduated`, `trial`, `dropped`, `untracked`) and `freshness` (`live`, `needsSync`, `syncing`),
21
+ and add `searchFrequencyRank`.
22
+ - Re-check graduated keywords ahead of trials, tune scan rate and depth with daily provider
23
+ ceilings, and let live searches win Keepa capacity over background work.
24
+
25
+ ### Fixed
26
+
27
+ - Answer live catalog searches before saving Product history, and record why failed searches
28
+ failed.
29
+ - Adapt Ads pacing to 429 responses and drop Keepa's no-category sentinel rank.
30
+
31
+ ## v0.12.0 - 2026-10-26
32
+
33
+ ### Added
34
+
35
+ - Collect keyword candidates once per Merch ASIN through Amazon Ads and once per tracked keyword
36
+ through autocomplete, sharing cached Jev decisions and popularity-based discovery scheduling.
37
+ - Add a persistent pause/resume control for the keyword pipeline, initialized paused on rollout.
38
+ - Show the live keyword directory with plain status badges, work-priority ordering, BSR popularity
39
+ and refresh cadence in a Products-style table. Record keyword lifecycle events in Logs.
40
+
41
+ ### Changed
42
+
43
+ - Breaking: keyword get/search now read the stored keyword directory; keyword search uses an opaque
44
+ cursor object. Remove Brand Analytics keyword history and its CLI/MCP operation.
45
+ - Retire Search Terms, Brand Analytics report ingestion, metrics and health checks. Migration drops
46
+ obsolete report tables and demand fields while retaining canonical keywords and product evidence.
47
+
5
48
  ## v0.11.0 - 2026-10-23
6
49
 
7
50
  ### Changed
@@ -1,5 +1,4 @@
1
1
  import { parseIntegerOption } from './cli-options.js';
2
- const KEYWORD_MARKETPLACE_ID = 'ATVPDKIKX0DER';
3
2
  export const runKeywordCommand = async (command, client, options, fail) => {
4
3
  if (command.verb !== 'results' && options.refresh) {
5
4
  fail('INVALID_INPUT', '--refresh is only supported for keyword results');
@@ -16,9 +15,6 @@ export const runKeywordCommand = async (command, client, options, fail) => {
16
15
  if (command.verb === 'search') {
17
16
  return await runKeywordSearch(command.args, client, options, fail);
18
17
  }
19
- if (command.verb === 'history') {
20
- return await runKeywordHistory(command.args, client, options, fail);
21
- }
22
18
  fail('UNKNOWN_COMMAND', 'Unknown Keyword command', {
23
19
  command: `keyword ${command.verb}`,
24
20
  });
@@ -27,21 +23,15 @@ const runKeywordGet = async (args, client, fail) => {
27
23
  const keyword = requireKeyword(args, fail, 'keyword get');
28
24
  return await client.keyword.get.query({
29
25
  keyword,
30
- marketplaceId: KEYWORD_MARKETPLACE_ID,
31
26
  });
32
27
  };
33
28
  const runKeywordSearch = async (args, client, options, fail) => {
34
29
  const text = requireKeyword(args, fail, 'keyword search');
35
30
  return await client.keyword.search.query({
36
31
  text,
37
- marketplaceId: KEYWORD_MARKETPLACE_ID,
38
- cursor: parseIntegerOption({
39
- value: options.cursor,
40
- optionName: 'cursor',
41
- min: 0,
42
- max: 1_000_000,
43
- defaultValue: 0,
44
- }, fail),
32
+ stage: keywordChoice(options.stage, KEYWORD_STAGES, 'stage', fail),
33
+ freshness: keywordChoice(options.freshness, KEYWORD_FRESHNESS, 'freshness', fail),
34
+ cursor: options.cursor ? parseKeywordCursor(options.cursor, fail) : undefined,
45
35
  limit: parseIntegerOption({
46
36
  value: options.limit,
47
37
  optionName: 'limit',
@@ -49,21 +39,6 @@ const runKeywordSearch = async (args, client, options, fail) => {
49
39
  max: 100,
50
40
  defaultValue: 25,
51
41
  }, fail),
52
- merchOnly: true,
53
- });
54
- };
55
- const runKeywordHistory = async (args, client, options, fail) => {
56
- const keyword = requireKeyword(args, fail, 'keyword history');
57
- return await client.keyword.history.query({
58
- keyword,
59
- marketplaceId: KEYWORD_MARKETPLACE_ID,
60
- rangeDays: parseIntegerOption({
61
- value: options.rangeDays ?? options.days,
62
- optionName: 'rangeDays',
63
- min: 7,
64
- max: 365,
65
- defaultValue: 90,
66
- }, fail),
67
42
  });
68
43
  };
69
44
  const requireKeyword = (args, fail, commandName) => {
@@ -73,3 +48,25 @@ const requireKeyword = (args, fail, commandName) => {
73
48
  }
74
49
  return keyword;
75
50
  };
51
+ const KEYWORD_STAGES = ['graduated', 'trial', 'dropped', 'untracked', 'all'];
52
+ const KEYWORD_FRESHNESS = ['live', 'needsSync', 'syncing', 'all'];
53
+ const keywordChoice = (value, choices, name, fail) => {
54
+ if (value === undefined)
55
+ return undefined;
56
+ const match = choices.find(candidate => candidate === value);
57
+ return match ?? fail('INVALID_INPUT', `--${name} must be ${choices.join(', ')}`);
58
+ };
59
+ const parseKeywordCursor = (value, fail) => {
60
+ try {
61
+ const parsed = JSON.parse(value);
62
+ const key = parsed?.key;
63
+ if (!Array.isArray(key) || key.length !== 7 || !key.slice(0, 6).every(part => typeof part === 'number' && Number.isFinite(part))
64
+ || typeof key[6] !== 'string' || typeof parsed.asOf !== 'string'
65
+ || (parsed.sort !== 'work' && parsed.sort !== 'newest'))
66
+ return fail('INVALID_INPUT', '--cursor must be the JSON nextCursor from keyword search');
67
+ return { key: [key[0], key[1], key[2], key[3], key[4], key[5], key[6]], asOf: parsed.asOf, sort: parsed.sort };
68
+ }
69
+ catch {
70
+ return fail('INVALID_INPUT', '--cursor must be valid JSON');
71
+ }
72
+ };
@@ -11,6 +11,8 @@ export interface CliOptionValues {
11
11
  includeOfficialBrands?: boolean;
12
12
  merch?: string;
13
13
  listingStatus?: string;
14
+ stage?: string;
15
+ freshness?: string;
14
16
  sort?: string;
15
17
  minBsr?: string;
16
18
  maxBsr?: string;
package/dist/index.js CHANGED
@@ -16,7 +16,6 @@ const SUPPORTED_COMMANDS = new Set([
16
16
  'product:history',
17
17
  'keyword:get',
18
18
  'keyword:search',
19
- 'keyword:history',
20
19
  'keyword:results',
21
20
  'auth:status',
22
21
  'auth:set',
@@ -38,7 +37,6 @@ const { positionals, values } = parseArgs({
38
37
  startAt: { type: 'string' },
39
38
  endAt: { type: 'string' },
40
39
  days: { type: 'string' },
41
- rangeDays: { type: 'string' },
42
40
  limit: { type: 'string' },
43
41
  bucket: { type: 'string' },
44
42
  cursor: { type: 'string' },
@@ -53,6 +51,8 @@ const { positionals, values } = parseArgs({
53
51
  includeOfficialBrands: { type: 'boolean' },
54
52
  merch: { type: 'string' },
55
53
  listingStatus: { type: 'string' },
54
+ stage: { type: 'string' },
55
+ freshness: { type: 'string' },
56
56
  sort: { type: 'string' },
57
57
  minBsr: { type: 'string' },
58
58
  maxBsr: { type: 'string' },
package/dist/usage.js CHANGED
@@ -20,7 +20,6 @@ export const printUsage = (paths) => {
20
20
  ' product history <ASIN>',
21
21
  ' keyword get <keyword>',
22
22
  ' keyword search <text>',
23
- ' keyword history <keyword>',
24
23
  ' keyword results <keyword>',
25
24
  ' auth status',
26
25
  ' auth set [apiKey]',
@@ -44,11 +43,10 @@ export const printUsage = (paths) => {
44
43
  ' --metrics <list> Product history metrics: salesRank,price (default: salesRank,price)',
45
44
  ' --bucket <unit> Product history buckets: auto,day,week,month (default: auto)',
46
45
  ' --days <N> Product history lookback (30-3650, default: 365)',
47
- ' --rangeDays <N> Keyword history lookback (7-365, default: 90)',
48
46
  ' --startAt <ISO> Product history range start (ISO date/time)',
49
47
  ' --endAt <ISO> Product history range end (ISO date/time)',
50
48
  ' --limit <N> Result limit',
51
- ' --cursor <value> Product search cursor or keyword search offset',
49
+ ' --cursor <value> Product search cursor or keyword nextCursor JSON',
52
50
  ' --sort <order> Product search order (default: relevance)',
53
51
  ' --textFields <scope> Product search text: all,title (default: all)',
54
52
  ' --minBsr/--maxBsr <N> Product sales-rank bounds',
@@ -63,6 +61,8 @@ export const printUsage = (paths) => {
63
61
  ' --facet <facet=name> Repeat for facet filters',
64
62
  ' --merch <value> true,false,unknown,all (default: true)',
65
63
  ' --listingStatus <value> active,deleted,all (default: active)',
64
+ ' --stage <value> Keyword search: graduated,trial,dropped,untracked,all (default: graduated)',
65
+ ' --freshness <value> Keyword search: live,needsSync,syncing,all (default: all)',
66
66
  ' --includeOfficialBrands Include known official brands in Product search',
67
67
  ' --stdin Read auth secret from standard input',
68
68
  '',
@@ -87,7 +87,7 @@ export const printUsage = (paths) => {
87
87
  ' rw product history B0DV53VS61 --bucket week',
88
88
  ' rw keyword get "retro gardening shirt"',
89
89
  ' rw keyword search "retro gardening" --limit 10',
90
- ' rw keyword history "retro gardening shirt" --rangeDays 90',
90
+ ' rw keyword search "retro gardening" --stage trial',
91
91
  ' RR_STORAGE_DIR=~/.config/rankwrangler rw config show',
92
92
  ' rw --version',
93
93
  ' rw changelog',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankwrangler/cli",
3
- "version": "0.11.0",
3
+ "version": "0.13.0",
4
4
  "description": "Official RankWrangler command line interface",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@merchbaseco/access": "0.4.0",
33
- "@rankwrangler/http-client": "^0.11.0",
33
+ "@rankwrangler/http-client": "^0.13.0",
34
34
  "keytar": "7.9.0"
35
35
  },
36
36
  "devDependencies": {