@rankwrangler/cli 0.12.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/dist/CHANGELOG.md CHANGED
@@ -2,6 +2,32 @@
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
+
5
31
  ## v0.12.0 - 2026-10-26
6
32
 
7
33
  ### Added
@@ -29,6 +29,8 @@ const runKeywordSearch = async (args, client, options, fail) => {
29
29
  const text = requireKeyword(args, fail, 'keyword search');
30
30
  return await client.keyword.search.query({
31
31
  text,
32
+ stage: keywordChoice(options.stage, KEYWORD_STAGES, 'stage', fail),
33
+ freshness: keywordChoice(options.freshness, KEYWORD_FRESHNESS, 'freshness', fail),
32
34
  cursor: options.cursor ? parseKeywordCursor(options.cursor, fail) : undefined,
33
35
  limit: parseIntegerOption({
34
36
  value: options.limit,
@@ -46,15 +48,23 @@ const requireKeyword = (args, fail, commandName) => {
46
48
  }
47
49
  return keyword;
48
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
+ };
49
59
  const parseKeywordCursor = (value, fail) => {
50
60
  try {
51
61
  const parsed = JSON.parse(value);
52
62
  const key = parsed?.key;
53
63
  if (!Array.isArray(key) || key.length !== 7 || !key.slice(0, 6).every(part => typeof part === 'number' && Number.isFinite(part))
54
- || typeof key[6] !== 'string' || typeof parsed.asOf !== 'string' || typeof parsed.trialTurn !== 'boolean'
64
+ || typeof key[6] !== 'string' || typeof parsed.asOf !== 'string'
55
65
  || (parsed.sort !== 'work' && parsed.sort !== 'newest'))
56
66
  return fail('INVALID_INPUT', '--cursor must be the JSON nextCursor from keyword search');
57
- return { key: [key[0], key[1], key[2], key[3], key[4], key[5], key[6]], asOf: parsed.asOf, trialTurn: parsed.trialTurn, sort: parsed.sort };
67
+ return { key: [key[0], key[1], key[2], key[3], key[4], key[5], key[6]], asOf: parsed.asOf, sort: parsed.sort };
58
68
  }
59
69
  catch {
60
70
  return fail('INVALID_INPUT', '--cursor must be valid JSON');
@@ -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
@@ -51,6 +51,8 @@ const { positionals, values } = parseArgs({
51
51
  includeOfficialBrands: { type: 'boolean' },
52
52
  merch: { type: 'string' },
53
53
  listingStatus: { type: 'string' },
54
+ stage: { type: 'string' },
55
+ freshness: { type: 'string' },
54
56
  sort: { type: 'string' },
55
57
  minBsr: { type: 'string' },
56
58
  maxBsr: { type: 'string' },
package/dist/usage.js CHANGED
@@ -61,6 +61,8 @@ export const printUsage = (paths) => {
61
61
  ' --facet <facet=name> Repeat for facet filters',
62
62
  ' --merch <value> true,false,unknown,all (default: true)',
63
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)',
64
66
  ' --includeOfficialBrands Include known official brands in Product search',
65
67
  ' --stdin Read auth secret from standard input',
66
68
  '',
@@ -85,6 +87,7 @@ export const printUsage = (paths) => {
85
87
  ' rw product history B0DV53VS61 --bucket week',
86
88
  ' rw keyword get "retro gardening shirt"',
87
89
  ' rw keyword search "retro gardening" --limit 10',
90
+ ' rw keyword search "retro gardening" --stage trial',
88
91
  ' RR_STORAGE_DIR=~/.config/rankwrangler rw config show',
89
92
  ' rw --version',
90
93
  ' rw changelog',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rankwrangler/cli",
3
- "version": "0.12.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.12.0",
33
+ "@rankwrangler/http-client": "^0.13.0",
34
34
  "keytar": "7.9.0"
35
35
  },
36
36
  "devDependencies": {